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.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   * The StretchBendType class defines one out-of-plane angle bending energy type.
66   *
67   * @author Michael J. Schnieders
68   * @since 1.0
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     * Constant <code>units=PI / 180.0</code>
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     * Atom class for this stretch-bend type.
94     */
95    public final int[] atomClasses;
96    /**
97     * Force constants (Kcal/mole/Angstrom-Degrees).
98     */
99    public final double[] forceConstants;
100 
101   /**
102    * StretchBendType Constructor.
103    *
104    * @param atomClasses    int[]
105    * @param forceConstants double[]
106    */
107   public StretchBendType(int[] atomClasses, double[] forceConstants) {
108     super(STRBND, sortKey(copyOf(atomClasses, 3)));
109     // Sort the atom classes and force constants in tandem.
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    * average.
124    *
125    * @param stretchBendType1 a {@link ffx.potential.parameters.StretchBendType} object.
126    * @param stretchBendType2 a {@link ffx.potential.parameters.StretchBendType} object.
127    * @param atomClasses      an array of {@link int} objects.
128    * @return a {@link ffx.potential.parameters.StretchBendType} object.
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    * Construct a StretchBendType from an input string.
149    *
150    * @param input  The overall input String.
151    * @param tokens The input String tokenized.
152    * @return a StretchBendType instance.
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    * This method sorts the atom classes as: min, c[1], max
177    *
178    * @param c atomClasses
179    * @return lookup key
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    * {@inheritDoc}
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    * {@inheritDoc}
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    * {@inheritDoc}
239    */
240   @Override
241   public int hashCode() {
242     return Arrays.hashCode(atomClasses);
243   }
244 
245   /**
246    * Increment the atom classes by a given value.
247    *
248    * @param increment The increment to apply to the atom classes.
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    * Remap new atom classes to known internal ones.
259    *
260    * @param typeMap a lookup between new atom types and known atom types.
261    * @return a {@link ffx.potential.parameters.StretchBendType} object.
262    */
263   public StretchBendType patchClasses(HashMap<AtomType, AtomType> typeMap) {
264     int count = 0;
265     int len = atomClasses.length;
266 
267     // Check if this StetchBendType contain 1 or 2 mapped atom classes.
268     for (AtomType newType : typeMap.keySet()) {
269       for (int atomClass : atomClasses) {
270         if (atomClass == newType.atomClass) {
271           count++;
272         }
273       }
274     }
275 
276     // If found, create a new StetchBendType that bridges to known classes.
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    * {@inheritDoc}
295    *
296    * <p>Nicely formatted Stretch-Bend string.
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    * Create an AmoebaStretchBendForce element.
306    *
307    * @param doc        the Document instance.
308    * @param forceField the ForceField to grab constants from.
309    * @return the AmoebaStretchBendForce element.
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    * Write StretchBendType to OpenMM XML format.
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     // Convert kcal/mol/A-degrees to KJ/mol/nm-radians
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 }