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.ANGTORS;
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.util.Arrays.copyOf;
61  
62  /**
63   * The AngleTorsionType class defines one angle-torsion energy type.
64   *
65   * @author Michael J. Schnieders
66   * @since 1.0
67   */
68  @FFXProperty(name = "angtors", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
69      [4 integers and 6 reals]
70      Provides the values for a single bond angle bending-torsional angle parameter.
71      The integer modifiers give the atom class numbers for the four kinds of atoms involved in the torsion and its contained angles.
72      The real number modifiers give the force constant values for both angles coupled with 1-, 2- and 3-fold torsional terms.
73      The default units for the force constants are kcal/mole/radian, but this can be controlled via the angtorunit keyword.
74      """)
75  public final class AngleTorsionType extends BaseType implements Comparator<String> {
76  
77    public static final double DEFAULT_ANGTOR_UNIT = 1.0 / DEGREES_PER_RADIAN;
78  
79    /**
80     * Convert angle-torsion to kcal/mole.
81     */
82    @FFXProperty(name = "angtorunit", propertyGroup = EnergyUnitConversion, defaultValue = "Pi/180", description = """
83        Sets the scale factor needed to convert the energy value computed by the angle bending-torsional angle
84        cross term into units of kcal/mole. The correct value is force field dependent and typically provided in the
85        header of the master force field parameter file.
86        """)
87    public double angtorunit = DEFAULT_ANGTOR_UNIT;
88  
89    /**
90     * A Logger for the AngleTorsionType class.
91     */
92    private static final Logger logger = Logger.getLogger(AngleTorsionType.class.getName());
93    /**
94     * Atom classes for this stretch-torsion type.
95     */
96    public final int[] atomClasses;
97    /**
98     * Force constants.
99     */
100   public final double[] forceConstants;
101 
102   /**
103    * AngleTorsionType Constructor.
104    *
105    * @param atomClasses    Atomic classes.
106    * @param forceConstants Force constants.
107    */
108   public AngleTorsionType(int[] atomClasses, double[] forceConstants) {
109     super(ANGTORS, sortKey(atomClasses));
110     this.atomClasses = atomClasses;
111     this.forceConstants = forceConstants;
112   }
113 
114   /**
115    * Average two AngleTorsionType instances.
116    *
117    * @param angleTorsionType1 First AngleTorsionType.
118    * @param angleTorsionType2 Second AngleTorsionType.
119    * @param atomClasses       Atom classes for the averaged type.
120    * @return A new AngleTorsionType with averaged force constants, or null if inputs are invalid.
121    */
122   public static AngleTorsionType average(@Nullable AngleTorsionType angleTorsionType1,
123                                          @Nullable AngleTorsionType angleTorsionType2,
124                                          @Nullable int[] atomClasses) {
125     if (angleTorsionType1 == null || angleTorsionType2 == null || atomClasses == null) {
126       return null;
127     }
128     int len = angleTorsionType1.forceConstants.length;
129     if (len != angleTorsionType2.forceConstants.length) {
130       return null;
131     }
132     double[] forceConstants = new double[len];
133     for (int i = 0; i < len; i++) {
134       forceConstants[i] =
135           (angleTorsionType1.forceConstants[i] + angleTorsionType2.forceConstants[i]) / 2.0;
136     }
137     return new AngleTorsionType(atomClasses, forceConstants);
138   }
139 
140   /**
141    * Construct an AngleTorsionType from an input string.
142    *
143    * @param input  The overall input String.
144    * @param tokens The input String tokenized.
145    * @return an AngleTorsionType instance.
146    */
147   public static AngleTorsionType parse(String input, String[] tokens) {
148     if (tokens.length < 10) {
149       logger.log(Level.WARNING, "Invalid ANGTORS type:\n{0}", input);
150     } else {
151       try {
152         int[] atomClasses = new int[4];
153         atomClasses[0] = parseInt(tokens[1]);
154         atomClasses[1] = parseInt(tokens[2]);
155         atomClasses[2] = parseInt(tokens[3]);
156         atomClasses[3] = parseInt(tokens[4]);
157         double[] constants = new double[6];
158         constants[0] = parseDouble(tokens[5]);
159         constants[1] = parseDouble(tokens[6]);
160         constants[2] = parseDouble(tokens[7]);
161         constants[3] = parseDouble(tokens[8]);
162         constants[4] = parseDouble(tokens[9]);
163         constants[5] = parseDouble(tokens[10]);
164         return new AngleTorsionType(atomClasses, constants);
165       } catch (NumberFormatException e) {
166         String message = "Exception parsing ANGTORS type:\n" + input + "\n";
167         logger.log(Level.SEVERE, message, e);
168       }
169     }
170     return null;
171   }
172 
173   /**
174    * This method sorts the atom classes for the angle-torsion.
175    *
176    * @param c atomClasses
177    * @return lookup key
178    * @since 1.0
179    */
180   public static String sortKey(int[] c) {
181     return c[0] + " " + c[1] + " " + c[2] + " " + c[3];
182   }
183 
184   /**
185    * {@inheritDoc}
186    *
187    * @since 1.0
188    */
189   @Override
190   public int compare(String s1, String s2) {
191     String[] keys1 = s1.split(" ");
192     String[] keys2 = s2.split(" ");
193     int[] c1 = new int[4];
194     int[] c2 = new int[4];
195 
196     for (int i = 0; i < 4; i++) {
197       c1[i] = parseInt(keys1[i]);
198       c2[i] = parseInt(keys2[i]);
199     }
200 
201     if (c1[1] < c2[1]) {
202       return -1;
203     } else if (c1[1] > c2[1]) {
204       return 1;
205     } else if (c1[2] < c2[2]) {
206       return -1;
207     } else if (c1[2] > c2[2]) {
208       return 1;
209     } else if (c1[0] < c2[0]) {
210       return -1;
211     } else if (c1[0] > c2[0]) {
212       return 1;
213     } else if (c1[3] < c2[3]) {
214       return -1;
215     } else if (c1[3] > c2[3]) {
216       return 1;
217     }
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     AngleTorsionType angleTorsionType = (AngleTorsionType) o;
234     return Arrays.equals(atomClasses, angleTorsionType.atomClasses);
235   }
236 
237   /**
238    * {@inheritDoc}
239    */
240   @Override
241   public int hashCode() {
242     return Arrays.hashCode(atomClasses);
243   }
244 
245   /**
246    * incrementClasses
247    *
248    * @param increment the value to increment the atom classes by.
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.AngleTorsionType} object.
262    */
263   public AngleTorsionType patchClasses(HashMap<AtomType, AtomType> typeMap) {
264     int count = 0;
265     int len = atomClasses.length;
266 
267     // Check if this Type contain 1 or 2 mapped atom classes.
268     for (AtomType newType : typeMap.keySet()) {
269 
270       for (int atomClass : atomClasses) {
271         if (atomClass == newType.atomClass) {
272           count++;
273         }
274       }
275     }
276 
277     // If found, create a new AngleTorsionType that bridges to known classes.
278     if (count == 1 || count == 2) {
279       int[] newClasses = copyOf(atomClasses, len);
280       for (AtomType newType : typeMap.keySet()) {
281         for (int i = 0; i < len; i++) {
282           if (atomClasses[i] == newType.atomClass) {
283             AtomType knownType = typeMap.get(newType);
284             newClasses[i] = knownType.atomClass;
285           }
286         }
287       }
288       return new AngleTorsionType(newClasses, forceConstants);
289     }
290     return null;
291   }
292 
293   /**
294    * {@inheritDoc}
295    *
296    * <p>Nicely formatted Angle-Torsion string.
297    */
298   @Override
299   public String toString() {
300     return format("angtors  %5d  %5d  %5d  %5d  %6.3f  %6.3f  %6.3f  %6.3f  %6.3f  %6.3f",
301         atomClasses[0], atomClasses[1], atomClasses[2], atomClasses[3], forceConstants[0],
302         forceConstants[1], forceConstants[2], forceConstants[3], forceConstants[4],
303         forceConstants[5]);
304   }
305 
306   /**
307    * Create an AmoebaAngleTorsionForce Element.
308    *
309    * @param doc        the Document instance.
310    * @param forceField the ForceField instance to grab constants from.
311    * @return the AmoebaAngleTorsionForce Element.
312    */
313   public static Element getXMLForce(Document doc, ForceField forceField) {
314     Map<String, AngleTorsionType> types = forceField.getAngleTorsionTypes();
315     if (!types.values().isEmpty()) {
316       Element node = doc.createElement("AmoebaAngleTorsionForce");
317       for (AngleTorsionType angleTorsionType : types.values()) {
318         node.appendChild(angleTorsionType.toXML(doc));
319       }
320       return node;
321     }
322     return null;
323   }
324 
325   /**
326    * Write AngleTorsionType to OpenMM XML format.
327    *
328    * @param doc the Document instance.
329    * @return the Torsion element.
330    */
331   public Element toXML(Document doc) {
332     Element node = doc.createElement("Torsion");
333     node.setAttribute("class1", format("%d", atomClasses[0]));
334     node.setAttribute("class2", format("%d", atomClasses[1]));
335     node.setAttribute("class3", format("%d", atomClasses[2]));
336     node.setAttribute("class4", format("%d", atomClasses[3]));
337     node.setAttribute("v11", format("%.17f", forceConstants[0] * KCAL_TO_KJ));
338     node.setAttribute("v12", format("%.17f", forceConstants[1] * KCAL_TO_KJ));
339     node.setAttribute("v13", format("%.17f", forceConstants[2] * KCAL_TO_KJ));
340     node.setAttribute("v21", format("%.17f", forceConstants[3] * KCAL_TO_KJ));
341     node.setAttribute("v22", format("%.17f", forceConstants[4] * KCAL_TO_KJ));
342     node.setAttribute("v23", format("%.17f", forceConstants[5] * KCAL_TO_KJ));
343     return node;
344   }
345 }