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