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.BOND;
53 import static ffx.utilities.Constants.ANG_TO_NM;
54 import static ffx.utilities.Constants.KCAL_TO_KJ;
55 import static ffx.utilities.Constants.NM_TO_ANG;
56 import static ffx.utilities.PropertyGroup.EnergyUnitConversion;
57 import static ffx.utilities.PropertyGroup.LocalGeometryFunctionalForm;
58 import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
59 import static java.lang.Double.parseDouble;
60 import static java.lang.Integer.parseInt;
61 import static java.lang.String.format;
62
63
64
65
66
67
68
69 @FFXProperty(name = "bond", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
70 [2 integers and 2 reals]
71 Provides the values for a single bond stretching parameter.
72 The integer modifiers give the atom class numbers for the two kinds of atoms involved in the bond which is to be defined.
73 The real number modifiers give the force constant value for the bond and the ideal bond length in Angstroms.
74 The default value of 1.0 is used, if the bondunit keyword is not given in the force field parameter file or the keyfile.
75 """)
76 public final class BondType extends BaseType implements Comparator<String> {
77
78 public static final double DEFAULT_BOND_UNIT = 1.0;
79
80
81
82
83 public static final double DEFAULT_BOND_CUBIC = 0.0;
84
85
86
87 public static final double DEFAULT_BOND_QUARTIC = 0.0;
88
89
90
91
92 @FFXProperty(name = "bondunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
93 Sets the scale factor needed to convert the energy value computed by the bond stretching potential into units of kcal/mole.
94 The correct value is force field dependent and typically provided in the header of the master force field parameter file.
95 """)
96 public double bondUnit = DEFAULT_BOND_UNIT;
97
98
99
100
101 @FFXProperty(name = "bond-cubic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
102 Sets the value of the cubic term in the Taylor series expansion form of the bond stretching potential energy.
103 The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
104 This term multiplied by the bond stretching energy unit conversion factor, the force constant,
105 and the cube of the deviation of the bond length from its ideal value gives the cubic contribution to the bond stretching energy.
106 The default value in the absence of the bond-cubic keyword is zero; i.e., the cubic bond stretching term is omitted.
107 """)
108 public double cubic = DEFAULT_BOND_CUBIC;
109
110
111
112
113 @FFXProperty(name = "bond-quartic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
114 Sets the value of the quartic term in the Taylor series expansion form of the bond stretching potential energy.
115 The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
116 This term multiplied by the bond stretching energy unit conversion factor, the force constant,
117 and the forth power of the deviation of the bond length from its ideal value gives the quartic contribution to the bond stretching energy.
118 The default value in the absence of the bond-quartic keyword is zero; i.e., the quartic bond stretching term is omitted.
119 """)
120 public double quartic = DEFAULT_BOND_QUARTIC;
121
122
123
124
125 private static final Logger logger = Logger.getLogger(BondType.class.getName());
126
127
128
129 public final int[] atomClasses;
130
131
132
133 public final double forceConstant;
134
135
136
137 public final double distance;
138
139
140
141
142 public final double flatBottomRadius;
143
144
145
146 public BondFunction bondFunction;
147
148
149
150
151
152
153
154
155 public BondType(int[] atomClasses, double forceConstant, double distance) {
156 this(atomClasses, forceConstant, distance, BondFunction.QUARTIC, 0.0);
157 }
158
159
160
161
162
163
164
165
166
167 public BondType(int[] atomClasses, double forceConstant, double distance,
168 BondFunction bondFunction) {
169 this(atomClasses, forceConstant, distance, bondFunction, 0.0);
170 }
171
172
173
174
175
176
177
178
179
180
181 public BondType(int[] atomClasses, double forceConstant, double distance, BondFunction bondFunction,
182 double flatBottomRadius) {
183 super(BOND, sortKey(atomClasses));
184 this.atomClasses = atomClasses;
185 this.forceConstant = forceConstant;
186 this.distance = distance;
187 this.bondFunction = bondFunction;
188 this.flatBottomRadius = flatBottomRadius;
189 assert (flatBottomRadius == 0 || bondFunction == BondFunction.FLAT_BOTTOM_HARMONIC);
190 }
191
192
193
194
195
196
197
198
199
200 public static BondType average(@Nullable BondType bondType1,
201 @Nullable BondType bondType2,
202 @Nullable int[] atomClasses) {
203 if (bondType1 == null || bondType2 == null || atomClasses == null) {
204 return null;
205 }
206 BondType.BondFunction bondFunction = bondType1.bondFunction;
207 if (bondFunction != bondType2.bondFunction) {
208 return null;
209 }
210
211 double forceConstant = (bondType1.forceConstant + bondType2.forceConstant) / 2.0;
212 double distance = (bondType1.distance + bondType2.distance) / 2.0;
213
214 return new BondType(atomClasses, forceConstant, distance, bondFunction);
215 }
216
217
218
219
220
221
222
223
224 public static BondType parse(String input, String[] tokens) {
225 if (tokens.length < 5) {
226 logger.log(Level.WARNING, "Invalid BOND type:\n{0}", input);
227 } else {
228 try {
229 int[] atomClasses = new int[2];
230 atomClasses[0] = parseInt(tokens[1]);
231 atomClasses[1] = parseInt(tokens[2]);
232 double forceConstant = parseDouble(tokens[3]);
233 double distance = parseDouble(tokens[4]);
234 return new BondType(atomClasses, forceConstant, distance);
235 } catch (NumberFormatException e) {
236 String message = "Exception parsing BOND type:\n" + input + "\n";
237 logger.log(Level.SEVERE, message, e);
238 }
239 }
240 return null;
241 }
242
243
244
245
246
247
248
249 public static String sortKey(int[] c) {
250 if (c == null || c.length != 2) {
251 return null;
252 }
253
254 int temp;
255 if (c[1] <= c[0]) {
256 temp = c[1];
257 c[1] = c[0];
258 c[0] = temp;
259 }
260
261 return c[0] + " " + c[1];
262 }
263
264
265
266
267 @Override
268 public int compare(String key1, String key2) {
269 String[] keys1 = key1.split(" ");
270 String[] keys2 = key2.split(" ");
271 int[] c1 = new int[2];
272 int[] c2 = new int[2];
273 for (int i = 0; i < 2; i++) {
274 c1[i] = parseInt(keys1[i]);
275 c2[i] = parseInt(keys2[i]);
276 }
277
278 if (c1[0] < c2[0]) {
279 return -1;
280 } else if (c1[0] > c2[0]) {
281 return 1;
282 } else if (c1[1] < c2[1]) {
283 return -1;
284 } else if (c1[1] > c2[1]) {
285 return 1;
286 }
287
288 return 0;
289 }
290
291
292
293
294 @Override
295 public boolean equals(Object o) {
296 if (this == o) {
297 return true;
298 }
299 if (o == null || getClass() != o.getClass()) {
300 return false;
301 }
302 BondType bondType = (BondType) o;
303 return Arrays.equals(atomClasses, bondType.atomClasses);
304 }
305
306
307
308
309 @Override
310 public int hashCode() {
311 return Arrays.hashCode(atomClasses);
312 }
313
314
315
316
317
318
319 public void incrementClasses(int increment) {
320 for (int i = 0; i < atomClasses.length; i++) {
321 atomClasses[i] += increment;
322 }
323 setKey(sortKey(atomClasses));
324 }
325
326
327
328
329
330
331
332 public BondType patchClasses(HashMap<AtomType, AtomType> typeMap) {
333
334 int count = 0;
335 int len = atomClasses.length;
336
337
338 for (AtomType newType : typeMap.keySet()) {
339
340 for (int atomClass : atomClasses) {
341 if (atomClass == newType.atomClass) {
342 count++;
343 }
344 }
345 }
346
347
348 if (count == 1) {
349 int[] newClasses = Arrays.copyOf(atomClasses, len);
350 for (AtomType newType : typeMap.keySet()) {
351 for (int i = 0; i < len; i++) {
352 if (atomClasses[i] == newType.atomClass) {
353 AtomType knownType = typeMap.get(newType);
354 newClasses[i] = knownType.atomClass;
355 }
356 }
357 }
358
359 return new BondType(newClasses, forceConstant, distance, bondFunction);
360 }
361 return null;
362 }
363
364 public void setBondFunction(BondFunction bondFunction) {
365 this.bondFunction = bondFunction;
366 }
367
368
369
370
371
372
373 @Override
374 public String toString() {
375 return format("bond %5d %5d %7.2f %7.4f", atomClasses[0], atomClasses[1], forceConstant,
376 distance);
377 }
378
379 public static Element getXMLForce(Document doc, ForceField forceField) {
380 Map<String, BondType> btMap = forceField.getBondTypes();
381 if (!btMap.values().isEmpty()) {
382 Element node = doc.createElement("AmoebaBondForce");
383 node.setAttribute("bond-cubic", format("%f", forceField.getDouble("bond-cubic", DEFAULT_BOND_CUBIC) * NM_TO_ANG));
384 node.setAttribute("bond-quartic", format("%f", forceField.getDouble("bond-quartic", DEFAULT_BOND_QUARTIC) * NM_TO_ANG * NM_TO_ANG));
385 for (BondType bondType : btMap.values()) {
386 node.appendChild(bondType.toXML(doc));
387 }
388 return node;
389 }
390 return null;
391 }
392
393
394
395
396 public Element toXML(Document doc) {
397 Element node = doc.createElement("Bond");
398 node.setAttribute("class1", format("%d", atomClasses[0]));
399 node.setAttribute("class2", format("%d", atomClasses[1]));
400 node.setAttribute("length", format("%f", distance * ANG_TO_NM));
401 node.setAttribute("k", format("%f", forceConstant * NM_TO_ANG * NM_TO_ANG * KCAL_TO_KJ));
402 return node;
403 }
404
405
406
407
408
409 public enum BondFunction {
410
411
412 HARMONIC("0.5*k*(r-r0)^2", false),
413
414
415 QUARTIC("0.5*k*dv^2*((1+cubic)*dv+(1+quartic)*dv^2);dv=r-r0", false),
416
417
418 FLAT_BOTTOM_HARMONIC(
419 "0.5*k*dv^2;dv=step(dv)*step(dv-fb)*(dv-fb)" + "+step(-dv)*step(-dv-fb)*(-dv-fb);dv=r-r0",
420 true),
421
422
423 FLAT_BOTTOM_QUARTIC("0.5*k*dv^2*((1+cubic)*dv+(1+quartic)*dv^2);"
424 + "dv=step(dv)*step(dv-fb)*(dv-fb)+step(-dv)*step(-dv-fb)*(-dv-fb);dv=r-r0", true);
425
426
427
428
429 private final String mathematicalForm;
430
431
432
433
434 private final boolean hasFlatBottom;
435
436
437
438
439
440
441
442 BondFunction(String mathForm, boolean flatBottom) {
443 this.mathematicalForm = mathForm;
444 this.hasFlatBottom = flatBottom;
445 }
446
447
448
449
450
451
452 public boolean hasFlatBottom() {
453 return hasFlatBottom;
454 }
455
456
457
458
459
460
461 public String toMathematicalForm() {
462 return mathematicalForm;
463 }
464 }
465 }