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.STRTORS;
52  import static ffx.utilities.Constants.ANG_TO_NM;
53  import static ffx.utilities.Constants.KCAL_TO_KJ;
54  import static ffx.utilities.PropertyGroup.EnergyUnitConversion;
55  import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
56  import static java.lang.Double.parseDouble;
57  import static java.lang.Integer.parseInt;
58  import static java.lang.String.format;
59  import static java.util.Arrays.copyOf;
60  
61  /**
62   * The StretchTorsionType class defines one stretch-torsion energy type.
63   *
64   * @author Michael J. Schnieders
65   * @since 1.0
66   */
67  @FFXProperty(name = "strtors", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
68      [2 integers and 1 real]
69      Provides the values for a single stretch-torsion cross term potential parameter.
70      The two integer modifiers give the atom class numbers for the atoms involved in the central bond of the torsional angles to be parameterized.
71      The real modifier gives the value of the stretch-torsion force constant for all torsional angles with the defined atom classes for the central bond.
72      The default units for the stretch-torsion force constant can be controlled via the strtorunit keyword.
73      """)
74  public final class StretchTorsionType extends BaseType implements Comparator<String> {
75  
76    public static final double DEFAULT_STRTOR_UNIT = 1.0;
77  
78    /**
79     * Unit conversion.
80     */
81    @FFXProperty(name = "strtorunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
82        Sets the scale factor needed to convert the energy value computed by the bond stretching-torsional
83        angle cross term potential into units of kcal/mole. The correct value is force field dependent and typically
84        provided in the header of the master force field parameter file.
85        """)
86    public double strTorUnit = DEFAULT_STRTOR_UNIT;
87  
88    /**
89     * A Logger for the StretchTorsionType class.
90     */
91    private static final Logger logger = Logger.getLogger(StretchTorsionType.class.getName());
92    /**
93     * Atom classes for this stretch-torsion type.
94     */
95    public final int[] atomClasses;
96    /**
97     * Force constants.
98     */
99    public final double[] forceConstants;
100 
101   /**
102    * StretchTorsionType Constructor.
103    *
104    * @param atomClasses    Atom classes.
105    * @param forceConstants Force constant.
106    */
107   public StretchTorsionType(int[] atomClasses, double[] forceConstants) {
108     // Pass the key from sorted classes to the super constructor.
109     super(STRTORS, sortKey(atomClasses));
110     this.atomClasses = atomClasses;
111     this.forceConstants = forceConstants;
112   }
113 
114   /**
115    * average.
116    *
117    * @param stretchTorsionType1 a {@link ffx.potential.parameters.StretchTorsionType} object.
118    * @param stretchTorsionType2 a {@link ffx.potential.parameters.StretchTorsionType} object.
119    * @param atomClasses         an array of {@link int} objects.
120    * @return a {@link ffx.potential.parameters.StretchTorsionType} object.
121    */
122   public static StretchTorsionType average(StretchTorsionType stretchTorsionType1,
123                                            StretchTorsionType stretchTorsionType2, int[] atomClasses) {
124     if (stretchTorsionType1 == null || stretchTorsionType2 == null || atomClasses == null) {
125       return null;
126     }
127     int len = stretchTorsionType1.forceConstants.length;
128     if (len != stretchTorsionType2.forceConstants.length) {
129       return null;
130     }
131     double[] forceConstants = new double[len];
132     for (int i = 0; i < len; i++) {
133       forceConstants[i] =
134           (stretchTorsionType1.forceConstants[i] + stretchTorsionType2.forceConstants[i]) / 2.0;
135     }
136     return new StretchTorsionType(atomClasses, forceConstants);
137   }
138 
139   /**
140    * Construct an StretchTorsionType from an input string.
141    *
142    * @param input  The overall input String.
143    * @param tokens The input String tokenized.
144    * @return an StretchTorsionType instance.
145    */
146   public static StretchTorsionType parse(String input, String[] tokens) {
147     if (tokens.length < 13) {
148       logger.log(Level.WARNING, "Invalid STRTORS type:\n{0}", input);
149     } else {
150       try {
151         int[] atomClasses = new int[4];
152         atomClasses[0] = parseInt(tokens[1]);
153         atomClasses[1] = parseInt(tokens[2]);
154         atomClasses[2] = parseInt(tokens[3]);
155         atomClasses[3] = parseInt(tokens[4]);
156         double[] constants = new double[9];
157         constants[0] = parseDouble(tokens[5]);
158         constants[1] = parseDouble(tokens[6]);
159         constants[2] = parseDouble(tokens[7]);
160         constants[3] = parseDouble(tokens[8]);
161         constants[4] = parseDouble(tokens[9]);
162         constants[5] = parseDouble(tokens[10]);
163         constants[6] = parseDouble(tokens[11]);
164         constants[7] = parseDouble(tokens[12]);
165         constants[8] = parseDouble(tokens[13]);
166         return new StretchTorsionType(atomClasses, constants);
167       } catch (NumberFormatException e) {
168         String message = "Exception parsing STRTORS 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 for the torsion.
177    *
178    * @param c atomClasses
179    * @return lookup key
180    * @since 1.0
181    */
182   public static String sortKey(int[] c) {
183     return c[0] + " " + c[1] + " " + c[2] + " " + c[3];
184   }
185 
186   /**
187    * {@inheritDoc}
188    *
189    * @since 1.0
190    */
191   @Override
192   public int compare(String s1, String s2) {
193     String[] keys1 = s1.split(" ");
194     String[] keys2 = s2.split(" ");
195     int[] c1 = new int[4];
196     int[] c2 = new int[4];
197 
198     for (int i = 0; i < 4; i++) {
199       c1[i] = parseInt(keys1[i]);
200       c2[i] = parseInt(keys2[i]);
201     }
202 
203     if (c1[1] < c2[1]) {
204       return -1;
205     } else if (c1[1] > c2[1]) {
206       return 1;
207     } else if (c1[2] < c2[2]) {
208       return -1;
209     } else if (c1[2] > c2[2]) {
210       return 1;
211     } else if (c1[0] < c2[0]) {
212       return -1;
213     } else if (c1[0] > c2[0]) {
214       return 1;
215     } else if (c1[3] < c2[3]) {
216       return -1;
217     } else if (c1[3] > c2[3]) {
218       return 1;
219     }
220 
221     return 0;
222   }
223 
224   /**
225    * {@inheritDoc}
226    */
227   @Override
228   public boolean equals(Object o) {
229     if (this == o) {
230       return true;
231     }
232     if (o == null || getClass() != o.getClass()) {
233       return false;
234     }
235     StretchTorsionType stretchTorsionType = (StretchTorsionType) o;
236     return Arrays.equals(atomClasses, stretchTorsionType.atomClasses);
237   }
238 
239   /**
240    * {@inheritDoc}
241    */
242   @Override
243   public int hashCode() {
244     return Arrays.hashCode(atomClasses);
245   }
246 
247   /**
248    * Increment the atom classes by a specified amount.
249    *
250    * @param increment The increment to add to the atom classes.
251    */
252   public void incrementClasses(int increment) {
253     for (int i = 0; i < atomClasses.length; i++) {
254       atomClasses[i] += increment;
255     }
256     setKey(sortKey(atomClasses));
257   }
258 
259   /**
260    * Remap new atom classes to known internal ones.
261    *
262    * @param typeMap a lookup between new atom types and known atom types.
263    * @return a {@link ffx.potential.parameters.StretchTorsionType} object.
264    */
265   public StretchTorsionType patchClasses(HashMap<AtomType, AtomType> typeMap) {
266     int count = 0;
267     int len = atomClasses.length;
268 
269     // Check if this Type contain 1 or 2 mapped atom classes.
270     for (AtomType newType : typeMap.keySet()) {
271       for (int atomClass : atomClasses) {
272         if (atomClass == newType.atomClass) {
273           count++;
274         }
275       }
276     }
277 
278     // If found, create a new StretchTorsionType that bridges to known classes.
279     if (count == 1 || count == 2) {
280       int[] newClasses = copyOf(atomClasses, len);
281       for (AtomType newType : typeMap.keySet()) {
282         for (int i = 0; i < len; i++) {
283           if (atomClasses[i] == newType.atomClass) {
284             AtomType knownType = typeMap.get(newType);
285             newClasses[i] = knownType.atomClass;
286           }
287         }
288       }
289       return new StretchTorsionType(newClasses, forceConstants);
290     }
291     return null;
292   }
293 
294   /**
295    * {@inheritDoc}
296    *
297    * <p>Nicely formatted Stretch-Torsion string.
298    */
299   @Override
300   public String toString() {
301     return format(
302         "strtors  %5d  %5d  %5d  %5d  %6.3f  %6.3f  %6.3f  %6.3f  %6.3f  %6.3f  %6.3f  %6.3f  %6.3f",
303         atomClasses[0], atomClasses[1], atomClasses[2], atomClasses[3], forceConstants[0],
304         forceConstants[1], forceConstants[2], forceConstants[3], forceConstants[4],
305         forceConstants[5], forceConstants[6], forceConstants[7], forceConstants[8]);
306   }
307 
308   /**
309    * Create an AmoebaStretchTorsionForce Element.
310    *
311    * @param doc        the Document instance.
312    * @param forceField the ForceField instance to grab constants from.
313    * @return the AmoebaStretchTorsionForce Element.
314    */
315   public static Element getXMLForce(Document doc, ForceField forceField) {
316     Map<String, StretchTorsionType> types = forceField.getStretchTorsionTypes();
317     if (!types.values().isEmpty()) {
318       Element node = doc.createElement("AmoebaStretchTorsionForce");
319       for (StretchTorsionType stretchTorsionType : types.values()) {
320         node.appendChild(stretchTorsionType.toXML(doc));
321       }
322       return node;
323     }
324     return null;
325   }
326 
327   /**
328    * Write StretchTorsionType to OpenMM XML format.
329    *
330    * @param doc the Document instance.
331    * @return the Torsion element.
332    */
333   public Element toXML(Document doc) {
334     Element node = doc.createElement("Torsion");
335     node.setAttribute("class1", format("%d", atomClasses[0]));
336     node.setAttribute("class2", format("%d", atomClasses[1]));
337     node.setAttribute("class3", format("%d", atomClasses[2]));
338     node.setAttribute("class4", format("%d", atomClasses[3]));
339     node.setAttribute("v11", format("%f", forceConstants[0] * KCAL_TO_KJ / ANG_TO_NM));
340     node.setAttribute("v12", format("%f", forceConstants[1] * KCAL_TO_KJ / ANG_TO_NM));
341     node.setAttribute("v13", format("%f", forceConstants[2] * KCAL_TO_KJ / ANG_TO_NM));
342     node.setAttribute("v21", format("%f", forceConstants[3] * KCAL_TO_KJ / ANG_TO_NM));
343     node.setAttribute("v22", format("%f", forceConstants[4] * KCAL_TO_KJ / ANG_TO_NM));
344     node.setAttribute("v23", format("%f", forceConstants[5] * KCAL_TO_KJ / ANG_TO_NM));
345     node.setAttribute("v31", format("%f", forceConstants[6] * KCAL_TO_KJ / ANG_TO_NM));
346     node.setAttribute("v32", format("%f", forceConstants[7] * KCAL_TO_KJ / ANG_TO_NM));
347     node.setAttribute("v33", format("%f", forceConstants[8] * KCAL_TO_KJ / ANG_TO_NM));
348     return node;
349   }
350 }