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 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   * The BondType class defines one harmonic bond stretch energy term.
65   *
66   * @author Michael J. Schnieders
67   * @since 1.0
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     * Default cubic coefficient in bond stretch potential.
82     */
83    public static final double DEFAULT_BOND_CUBIC = 0.0;
84    /**
85     * Default quartic coefficient in bond stretch potential.
86     */
87    public static final double DEFAULT_BOND_QUARTIC = 0.0;
88  
89    /**
90     * Convert bond stretch energy to kcal/mole.
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     * Cubic coefficient in bond stretch potential.
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    * Quartic coefficient in bond stretch potential.
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    * A Logger for the BondType class.
124    */
125   private static final Logger logger = Logger.getLogger(BondType.class.getName());
126   /**
127    * Atom classes that form this bond stretch.
128    */
129   public final int[] atomClasses;
130   /**
131    * Force constant (Kcal/mol/A^2).
132    */
133   public final double forceConstant;
134   /**
135    * Equilibrium separation (Angstroms).
136    */
137   public final double distance;
138   /**
139    * Radius of a flat bottom where energy and force is 0; typically used for restraints. Will almost
140    * always be 0.
141    */
142   public final double flatBottomRadius;
143   /**
144    * The function used by the bond: harmonic or quartic with flat-bottom variants.
145    */
146   public BondFunction bondFunction;
147 
148   /**
149    * The default BondType constructor defines use of the Quartic BondFunction.
150    *
151    * @param atomClasses   int[]
152    * @param forceConstant double
153    * @param distance      double
154    */
155   public BondType(int[] atomClasses, double forceConstant, double distance) {
156     this(atomClasses, forceConstant, distance, BondFunction.QUARTIC, 0.0);
157   }
158 
159   /**
160    * BondType constructor.
161    *
162    * @param atomClasses   int[]
163    * @param forceConstant double
164    * @param distance      double
165    * @param bondFunction  the BondFunction type to apply.
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    * BondType constructor.
174    *
175    * @param atomClasses      int[]
176    * @param forceConstant    double
177    * @param distance         double
178    * @param bondFunction     the BondFunction type to apply.
179    * @param flatBottomRadius a double.
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    * Average two BondType instances. The atom classes that define the new type must be supplied.
194    *
195    * @param bondType1   the first {@link ffx.potential.parameters.BondType} object.
196    * @param bondType2   the second {@link ffx.potential.parameters.BondType} object.
197    * @param atomClasses the atom classes that define the new type.
198    * @return a {@link ffx.potential.parameters.BondType} object or null if the bond functions differ.
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    * Construct a BondType from an input string.
219    *
220    * @param input  The overall input String.
221    * @param tokens The input String tokenized.
222    * @return a BondType instance.
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    * This method sorts the atom classes as: min, max
245    *
246    * @param c atomClasses
247    * @return lookup key
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    * {@inheritDoc}
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    * {@inheritDoc}
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    * {@inheritDoc}
308    */
309   @Override
310   public int hashCode() {
311     return Arrays.hashCode(atomClasses);
312   }
313 
314   /**
315    * incrementClasses
316    *
317    * @param increment The increment to apply to the atom classes.
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    * Remap new atom classes to known internal ones.
328    *
329    * @param typeMap a lookup between new atom types and known atom types.
330    * @return a {@link ffx.potential.parameters.BondType} object.
331    */
332   public BondType patchClasses(HashMap<AtomType, AtomType> typeMap) {
333 
334     int count = 0;
335     int len = atomClasses.length;
336 
337     // Look for new BondTypes that contain one mapped atom class.
338     for (AtomType newType : typeMap.keySet()) {
339 
340       for (int atomClass : atomClasses) {
341         if (atomClass == newType.atomClass) {
342           count++;
343         }
344       }
345     }
346 
347     // If found, create a new BondType that bridges to known classes.
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    * {@inheritDoc}
370    *
371    * <p>Nicely formatted bond stretch string.
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    * Write BondType to OpenMM XML format.
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    * Describes the function used by the bond. Currently supported: harmonic and quartic with
407    * flat-bottom variants.
408    */
409   public enum BondFunction {
410 
411     // Harmonic bond function.
412     HARMONIC("0.5*k*(r-r0)^2", false),
413 
414     // Quartic bond function.
415     QUARTIC("0.5*k*dv^2*((1+cubic)*dv+(1+quartic)*dv^2);dv=r-r0", false),
416 
417     // Flat bottom harmonic bond function for restraints.
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     // Flat bottom Quartic bond function for restraints.
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      * String representation of the mathematical form.
428      */
429     private final String mathematicalForm;
430 
431     /**
432      * Flag to indicate if the bond function has a flat bottom.
433      */
434     private final boolean hasFlatBottom;
435 
436     /**
437      * BondFunction constructor.
438      *
439      * @param mathForm   String representation of the mathematical form.
440      * @param flatBottom Flag to indicate if the bond function has a flat bottom.
441      */
442     BondFunction(String mathForm, boolean flatBottom) {
443       this.mathematicalForm = mathForm;
444       this.hasFlatBottom = flatBottom;
445     }
446 
447     /**
448      * Returns whether this BondFunction has a flat bottom.
449      *
450      * @return Flat bottom.
451      */
452     public boolean hasFlatBottom() {
453       return hasFlatBottom;
454     }
455 
456     /**
457      * Returns the form of this bond as a mathematical expression parsable by OpenMM.
458      *
459      * @return A string describing mathematical form.
460      */
461     public String toMathematicalForm() {
462       return mathematicalForm;
463     }
464   }
465 }