View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
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.BOND;
52  import static ffx.utilities.Constants.ANG_TO_NM;
53  import static ffx.utilities.Constants.KCAL_TO_KJ;
54  import static ffx.utilities.Constants.NM_TO_ANG;
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  
62  /**
63   * The BondType class defines one harmonic bond stretch energy term.
64   *
65   * @author Michael J. Schnieders
66   * @since 1.0
67   */
68  @FFXProperty(name = "bond", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
69      [2 integers and 2 reals]
70      Provides the values for a single bond stretching parameter.
71      The integer modifiers give the atom class numbers for the two kinds of atoms involved in the bond which is to be defined.
72      The real number modifiers give the force constant value for the bond and the ideal bond length in Angstroms.
73      The default value of 1.0 is used, if the bondunit keyword is not given in the force field parameter file or the keyfile.
74      """)
75  public final class BondType extends BaseType implements Comparator<String> {
76  
77    public static final double DEFAULT_BOND_UNIT = 1.0;
78  
79    /**
80     * Default cubic coefficient in bond stretch potential.
81     */
82    public static final double DEFAULT_BOND_CUBIC = 0.0;
83    /**
84     * Default quartic coefficient in bond stretch potential.
85     */
86    public static final double DEFAULT_BOND_QUARTIC = 0.0;
87  
88    /**
89     * Convert bond stretch energy to kcal/mole.
90     */
91    @FFXProperty(name = "bondunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
92        Sets the scale factor needed to convert the energy value computed by the bond stretching potential into units of kcal/mole.
93        The correct value is force field dependent and typically provided in the header of the master force field parameter file.
94        """)
95    public double bondUnit = DEFAULT_BOND_UNIT;
96  
97    /**
98     * Cubic coefficient in bond stretch potential.
99     */
100   @FFXProperty(name = "bond-cubic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
101       Sets the value of the cubic term in the Taylor series expansion form of the bond stretching potential energy.
102       The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
103       This term multiplied by the bond stretching energy unit conversion factor, the force constant,
104       and the cube of the deviation of the bond length from its ideal value gives the cubic contribution to the bond stretching energy.
105       The default value in the absence of the bond-cubic keyword is zero; i.e., the cubic bond stretching term is omitted.
106       """)
107   public double cubic = DEFAULT_BOND_CUBIC;
108 
109   /**
110    * Quartic coefficient in bond stretch potential.
111    */
112   @FFXProperty(name = "bond-quartic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
113       Sets the value of the quartic term in the Taylor series expansion form of the bond stretching potential energy.
114       The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
115       This term multiplied by the bond stretching energy unit conversion factor, the force constant,
116       and the forth power of the deviation of the bond length from its ideal value gives the quartic contribution to the bond stretching energy.
117       The default value in the absence of the bond-quartic keyword is zero; i.e., the quartic bond stretching term is omitted.
118       """)
119   public double quartic = DEFAULT_BOND_QUARTIC;
120 
121   /**
122    * A Logger for the BondType class.
123    */
124   private static final Logger logger = Logger.getLogger(BondType.class.getName());
125   /**
126    * Atom classes that form this bond stretch.
127    */
128   public final int[] atomClasses;
129   /**
130    * Force constant (Kcal/mol/A^2).
131    */
132   public final double forceConstant;
133   /**
134    * Equilibrium separation (Angstroms).
135    */
136   public final double distance;
137   /**
138    * Radius of a flat bottom where energy and force is 0; typically used for restraints. Will almost
139    * always be 0.
140    */
141   public final double flatBottomRadius;
142   /**
143    * The function used by the bond: harmonic or quartic with flat-bottom variants.
144    */
145   public BondFunction bondFunction;
146 
147   /**
148    * The default BondType constructor defines use of the Quartic BondFunction.
149    *
150    * @param atomClasses   int[]
151    * @param forceConstant double
152    * @param distance      double
153    */
154   public BondType(int[] atomClasses, double forceConstant, double distance) {
155     this(atomClasses, forceConstant, distance, BondFunction.QUARTIC, 0.0);
156   }
157 
158   /**
159    * BondType constructor.
160    *
161    * @param atomClasses   int[]
162    * @param forceConstant double
163    * @param distance      double
164    * @param bondFunction  the BondFunction type to apply.
165    */
166   public BondType(int[] atomClasses, double forceConstant, double distance,
167                   BondFunction bondFunction) {
168     this(atomClasses, forceConstant, distance, bondFunction, 0.0);
169   }
170 
171   /**
172    * BondType constructor.
173    *
174    * @param atomClasses      int[]
175    * @param forceConstant    double
176    * @param distance         double
177    * @param bondFunction     the BondFunction type to apply.
178    * @param flatBottomRadius a double.
179    */
180   public BondType(int[] atomClasses, double forceConstant, double distance, BondFunction bondFunction,
181                   double flatBottomRadius) {
182     super(BOND, sortKey(atomClasses));
183     this.atomClasses = atomClasses;
184     this.forceConstant = forceConstant;
185     this.distance = distance;
186     this.bondFunction = bondFunction;
187     this.flatBottomRadius = flatBottomRadius;
188     assert (flatBottomRadius == 0 || bondFunction == BondFunction.FLAT_BOTTOM_HARMONIC);
189   }
190 
191   /**
192    * Average two BondType instances. The atom classes that define the new type must be supplied.
193    *
194    * @param bondType1   a {@link ffx.potential.parameters.BondType} object.
195    * @param bondType2   a {@link ffx.potential.parameters.BondType} object.
196    * @param atomClasses an array of {@link int} objects.
197    * @return a {@link ffx.potential.parameters.BondType} object.
198    */
199   public static BondType average(BondType bondType1, BondType bondType2, int[] atomClasses) {
200     if (bondType1 == null || bondType2 == null || atomClasses == null) {
201       return null;
202     }
203     BondType.BondFunction bondFunction = bondType1.bondFunction;
204     if (bondFunction != bondType2.bondFunction) {
205       return null;
206     }
207 
208     double forceConstant = (bondType1.forceConstant + bondType2.forceConstant) / 2.0;
209     double distance = (bondType1.distance + bondType2.distance) / 2.0;
210 
211     return new BondType(atomClasses, forceConstant, distance, bondFunction);
212   }
213 
214   /**
215    * Construct a BondType from an input string.
216    *
217    * @param input  The overall input String.
218    * @param tokens The input String tokenized.
219    * @return a BondType instance.
220    */
221   public static BondType parse(String input, String[] tokens) {
222     if (tokens.length < 5) {
223       logger.log(Level.WARNING, "Invalid BOND type:\n{0}", input);
224     } else {
225       try {
226         int[] atomClasses = new int[2];
227         atomClasses[0] = parseInt(tokens[1]);
228         atomClasses[1] = parseInt(tokens[2]);
229         double forceConstant = parseDouble(tokens[3]);
230         double distance = parseDouble(tokens[4]);
231         return new BondType(atomClasses, forceConstant, distance);
232       } catch (NumberFormatException e) {
233         String message = "Exception parsing BOND type:\n" + input + "\n";
234         logger.log(Level.SEVERE, message, e);
235       }
236     }
237     return null;
238   }
239 
240   /**
241    * This method sorts the atom classes as: min, max
242    *
243    * @param c atomClasses
244    * @return lookup key
245    */
246   public static String sortKey(int[] c) {
247     if (c == null || c.length != 2) {
248       return null;
249     }
250 
251     int temp;
252     if (c[1] <= c[0]) {
253       temp = c[1];
254       c[1] = c[0];
255       c[0] = temp;
256     }
257 
258     return c[0] + " " + c[1];
259   }
260 
261   /**
262    * {@inheritDoc}
263    */
264   @Override
265   public int compare(String key1, String key2) {
266     String[] keys1 = key1.split(" ");
267     String[] keys2 = key2.split(" ");
268     int[] c1 = new int[2];
269     int[] c2 = new int[2];
270     for (int i = 0; i < 2; i++) {
271       c1[i] = parseInt(keys1[i]);
272       c2[i] = parseInt(keys2[i]);
273     }
274 
275     if (c1[0] < c2[0]) {
276       return -1;
277     } else if (c1[0] > c2[0]) {
278       return 1;
279     } else if (c1[1] < c2[1]) {
280       return -1;
281     } else if (c1[1] > c2[1]) {
282       return 1;
283     }
284 
285     return 0;
286   }
287 
288   /**
289    * {@inheritDoc}
290    */
291   @Override
292   public boolean equals(Object o) {
293     if (this == o) {
294       return true;
295     }
296     if (o == null || getClass() != o.getClass()) {
297       return false;
298     }
299     BondType bondType = (BondType) o;
300     return Arrays.equals(atomClasses, bondType.atomClasses);
301   }
302 
303   /**
304    * {@inheritDoc}
305    */
306   @Override
307   public int hashCode() {
308     return Arrays.hashCode(atomClasses);
309   }
310 
311   /**
312    * incrementClasses
313    *
314    * @param increment The increment to apply to the atom classes.
315    */
316   public void incrementClasses(int increment) {
317     for (int i = 0; i < atomClasses.length; i++) {
318       atomClasses[i] += increment;
319     }
320     setKey(sortKey(atomClasses));
321   }
322 
323   /**
324    * Remap new atom classes to known internal ones.
325    *
326    * @param typeMap a lookup between new atom types and known atom types.
327    * @return a {@link ffx.potential.parameters.BondType} object.
328    */
329   public BondType patchClasses(HashMap<AtomType, AtomType> typeMap) {
330 
331     int count = 0;
332     int len = atomClasses.length;
333 
334     // Look for new BondTypes that contain one mapped atom class.
335     for (AtomType newType : typeMap.keySet()) {
336 
337       for (int atomClass : atomClasses) {
338         if (atomClass == newType.atomClass) {
339           count++;
340         }
341       }
342     }
343 
344     // If found, create a new BondType that bridges to known classes.
345     if (count == 1) {
346       int[] newClasses = Arrays.copyOf(atomClasses, len);
347       for (AtomType newType : typeMap.keySet()) {
348         for (int i = 0; i < len; i++) {
349           if (atomClasses[i] == newType.atomClass) {
350             AtomType knownType = typeMap.get(newType);
351             newClasses[i] = knownType.atomClass;
352           }
353         }
354       }
355 
356       return new BondType(newClasses, forceConstant, distance, bondFunction);
357     }
358     return null;
359   }
360 
361   public void setBondFunction(BondFunction bondFunction) {
362     this.bondFunction = bondFunction;
363   }
364 
365   /**
366    * {@inheritDoc}
367    *
368    * <p>Nicely formatted bond stretch string.
369    */
370   @Override
371   public String toString() {
372     return format("bond  %5d  %5d  %7.2f  %7.4f", atomClasses[0], atomClasses[1], forceConstant,
373         distance);
374   }
375 
376   public static Element getXMLForce(Document doc, ForceField forceField) {
377     Map<String, BondType> btMap = forceField.getBondTypes();
378     if (!btMap.values().isEmpty()) {
379       Element node = doc.createElement("AmoebaBondForce");
380       node.setAttribute("bond-cubic", format("%f", forceField.getDouble("bond-cubic", DEFAULT_BOND_CUBIC) * NM_TO_ANG));
381       node.setAttribute("bond-quartic", format("%f", forceField.getDouble("bond-quartic", DEFAULT_BOND_QUARTIC) * NM_TO_ANG * NM_TO_ANG));
382       for (BondType bondType : btMap.values()) {
383         node.appendChild(bondType.toXML(doc));
384       }
385       return node;
386     }
387     return null;
388   }
389 
390   /**
391    * Write BondType to OpenMM XML format.
392    */
393   public Element toXML(Document doc) {
394     Element node = doc.createElement("Bond");
395     node.setAttribute("class1", format("%d", atomClasses[0]));
396     node.setAttribute("class2", format("%d", atomClasses[1]));
397     node.setAttribute("length", format("%f", distance * ANG_TO_NM));
398     node.setAttribute("k", format("%f", forceConstant * NM_TO_ANG * NM_TO_ANG * KCAL_TO_KJ));
399     return node;
400   }
401 
402   /**
403    * Describes the function used by the bond. Currently supported: harmonic and quartic with
404    * flat-bottom variants.
405    */
406   public enum BondFunction {
407 
408     // Harmonic bond function.
409     HARMONIC("0.5*k*(r-r0)^2", false),
410 
411     // Quartic bond function.
412     QUARTIC("0.5*k*dv^2*((1+cubic)*dv+(1+quartic)*dv^2);dv=r-r0", false),
413 
414     // Flat bottom harmonic bond function for restraints.
415     FLAT_BOTTOM_HARMONIC(
416         "0.5*k*dv^2;dv=step(dv)*step(dv-fb)*(dv-fb)" + "+step(-dv)*step(-dv-fb)*(-dv-fb);dv=r-r0",
417         true),
418 
419     // Flat bottom Quartic bond function for restraints.
420     FLAT_BOTTOM_QUARTIC("0.5*k*dv^2*((1+cubic)*dv+(1+quartic)*dv^2);"
421         + "dv=step(dv)*step(dv-fb)*(dv-fb)+step(-dv)*step(-dv-fb)*(-dv-fb);dv=r-r0", true);
422 
423     /**
424      * String representation of the mathematical form.
425      */
426     private final String mathematicalForm;
427 
428     /**
429      * Flag to indicate if the bond function has a flat bottom.
430      */
431     private final boolean hasFlatBottom;
432 
433     /**
434      * BondFunction constructor.
435      *
436      * @param mathForm   String representation of the mathematical form.
437      * @param flatBottom Flag to indicate if the bond function has a flat bottom.
438      */
439     BondFunction(String mathForm, boolean flatBottom) {
440       this.mathematicalForm = mathForm;
441       this.hasFlatBottom = flatBottom;
442     }
443 
444     /**
445      * Returns whether this BondFunction has a flat bottom.
446      *
447      * @return Flat bottom.
448      */
449     public boolean hasFlatBottom() {
450       return hasFlatBottom;
451     }
452 
453     /**
454      * Returns the form of this bond as a mathematical expression parsable by OpenMM.
455      *
456      * @return A string describing mathematical form.
457      */
458     public String toMathematicalForm() {
459       return mathematicalForm;
460     }
461   }
462 }