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-2024.
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 static ffx.potential.parameters.ForceField.ForceFieldType.IMPTORS;
41  import static ffx.utilities.PropertyGroup.EnergyUnitConversion;
42  import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
43  import static java.lang.Double.parseDouble;
44  import static java.lang.Integer.parseInt;
45  import static org.apache.commons.math3.util.FastMath.cos;
46  import static org.apache.commons.math3.util.FastMath.sin;
47  import static org.apache.commons.math3.util.FastMath.toRadians;
48  
49  import ffx.utilities.FFXProperty;
50  
51  import java.util.Arrays;
52  import java.util.Comparator;
53  import java.util.HashMap;
54  import java.util.logging.Level;
55  import java.util.logging.Logger;
56  
57  /**
58   * The ImproperTorsionType class defines an improper torsion.
59   *
60   * @author Michael J. Schnieders
61   * @since 1.0
62   */
63  @FFXProperty(name = "imptors", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
64      [4 integers and up to 3 real/real/integer triples]
65      Provides the values for a single AMBER-style improper torsional angle parameter.
66      The first four integer modifiers give the atom class numbers for the atoms involved in the improper torsional angle to be defined.
67      By convention, the third atom class of the four is the trigonal atom on which the improper torsion is centered.
68      The torsional angle computed is literally that defined by the four atom classes in the order specified by the keyword.
69      Each of the remaining triples of real/real/integer modifiers give the half-amplitude,
70      phase offset in degrees and periodicity of a particular improper torsional term, respectively.
71      Periodicities through 3-fold are allowed for improper torsional parameters.
72      """)
73  public final class ImproperTorsionType extends BaseType implements Comparator<String> {
74  
75    /**
76     * A Logger for the ImproperTorsionType class.
77     */
78    private static final Logger logger = Logger.getLogger(ImproperTorsionType.class.getName());
79  
80    /**
81     * Atom classes that for this Improper Torsion angle.
82     */
83    public final int[] atomClasses;
84    /**
85     * Force constant in kcal/mol.
86     */
87    public final double k;
88    /**
89     * Phases in degrees.
90     */
91    public final double phase;
92    /**
93     * Periodicity (should be 2 for an Improper Torsion).
94     */
95    public final int periodicity;
96    /**
97     * Value of cos(toRadians(phase)).
98     */
99    public final double cos;
100   /**
101    * Value of sin(toRadians(phase)).
102    */
103   public final double sin;
104 
105   /**
106    * Convert angle bending energy to kcal/mole.
107    */
108   @FFXProperty(name = "imptorunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
109       Sets the scale factor needed to convert the energy value computed by the AMBER-style improper
110       torsional angle potential into units of kcal/mole.
111       The correct value is force field dependent and typically provided in the header of the master force field parameter file.
112       """)
113   public double impTorUnit = DEFAULT_IMPTOR_UNIT;
114   public static final double DEFAULT_IMPTOR_UNIT = 1.0;
115 
116   /**
117    * TorsionType Constructor.
118    *
119    * @param atomClasses Atom classes.
120    * @param k           Force constant.
121    * @param phase       The phase.
122    * @param periodicity The periodicity.
123    */
124   public ImproperTorsionType(int[] atomClasses, double k, double phase, int periodicity) {
125     super(IMPTORS, sortKey(atomClasses));
126     this.atomClasses = atomClasses;
127     double symm = 1.0;
128     this.periodicity = periodicity;
129     this.k = k / symm;
130     this.phase = phase;
131     cos = cos(toRadians(phase));
132     sin = sin(toRadians(phase));
133 
134     assert (periodicity == 2);
135   }
136 
137   /**
138    * Average two ImproperTorsionType instances. The atom classes that define the new type must be
139    * supplied.
140    *
141    * @param improperTorsionType1 a {@link ffx.potential.parameters.ImproperTorsionType} object.
142    * @param improperTorsionType2 a {@link ffx.potential.parameters.ImproperTorsionType} object.
143    * @param atomClasses          an array of {@link int} objects.
144    * @return a {@link ffx.potential.parameters.ImproperTorsionType} object.
145    */
146   public static ImproperTorsionType average(ImproperTorsionType improperTorsionType1,
147                                             ImproperTorsionType improperTorsionType2, int[] atomClasses) {
148 
149     if (improperTorsionType1 == null || improperTorsionType2 == null || atomClasses == null) {
150       return null;
151     }
152 
153     int periodicity = improperTorsionType1.periodicity;
154     if (periodicity != improperTorsionType2.periodicity) {
155       return null;
156     }
157 
158     double forceConstant = (improperTorsionType1.k + improperTorsionType2.k) / 2.0;
159     double phase = (improperTorsionType1.phase + improperTorsionType2.phase) / 2.0;
160 
161     return new ImproperTorsionType(atomClasses, forceConstant, phase, periodicity);
162   }
163 
164   /**
165    * Construct an ImproperTorsionType from an input string.
166    *
167    * @param input  The overall input String.
168    * @param tokens The input String tokenized.
169    * @return an ImproperTorsionType instance.
170    */
171   public static ImproperTorsionType parse(String input, String[] tokens) {
172     if (tokens.length < 8) {
173       logger.log(Level.WARNING, "Invalid IMPTORS type:\n{0}", input);
174     } else {
175       try {
176         int[] atomClasses = new int[4];
177         atomClasses[0] = parseInt(tokens[1]);
178         atomClasses[1] = parseInt(tokens[2]);
179         atomClasses[2] = parseInt(tokens[3]);
180         atomClasses[3] = parseInt(tokens[4]);
181         double k = parseDouble(tokens[5]);
182         double phase = parseDouble(tokens[6]);
183         int period = parseInt(tokens[7]);
184         return new ImproperTorsionType(atomClasses, k, phase, period);
185       } catch (NumberFormatException e) {
186         String message = "Exception parsing IMPTORS type:\n" + input + "\n";
187         logger.log(Level.SEVERE, message, e);
188       }
189     }
190     return null;
191   }
192 
193   /**
194    * This method sorts the atom classes for the improper torsion.
195    *
196    * @param c atomClasses
197    * @return lookup key
198    * @since 1.0
199    */
200   public static String sortKey(int[] c) {
201     if (c == null || c.length != 4) {
202       return null;
203     }
204     return c[0] + " " + c[1] + " " + c[2] + " " + c[3];
205   }
206 
207   /**
208    * Returns true if the atoms can be assigned this improperTorsionType.
209    *
210    * @param inputClasses          The atom classes will be re-ordered if its member atoms match this
211    *                              ImproperTorsionType. The trigonal atom will not change position.
212    * @param allowInitialWildCards Allow wildcard match to first two classes.
213    * @param allowFinalWildCard    Allow wildcard match for final class.
214    * @return True if this torsionType is assignable to the atom array.
215    */
216   public boolean assigned(int[] inputClasses, boolean allowInitialWildCards,
217                           boolean allowFinalWildCard) {
218     // Assign the trigonal atom.
219     if (inputClasses[2] != atomClasses[2]) {
220       return false;
221     }
222 
223     // Assign the final atom.
224     if (inputClasses[3] == atomClasses[3] || (atomClasses[3] == 0 && allowFinalWildCard)) {
225       // do nothing.
226     } else if (inputClasses[1] == atomClasses[3]) {
227       int temp = inputClasses[3];
228       inputClasses[3] = inputClasses[1];
229       inputClasses[1] = temp;
230     } else if (inputClasses[0] == atomClasses[3]) {
231       int temp = inputClasses[3];
232       inputClasses[3] = inputClasses[0];
233       inputClasses[0] = temp;
234     } else {
235       return false;
236     }
237 
238     // Assign the second atom.
239     if (inputClasses[1] == atomClasses[1] || (atomClasses[1] == 0 && allowInitialWildCards)) {
240       // Do nothing.
241     } else if (inputClasses[0] == atomClasses[1]) {
242       int temp = inputClasses[1];
243       inputClasses[1] = inputClasses[0];
244       inputClasses[0] = temp;
245     } else {
246       return false;
247     }
248 
249     // Assign the first atom.
250     return (inputClasses[0] == atomClasses[0] || (atomClasses[0] == 0 && allowInitialWildCards));
251   }
252 
253   /**
254    * {@inheritDoc}
255    *
256    * <p>Implements the Comparator interface.
257    *
258    * @since 1.0
259    */
260   @Override
261   public int compare(String s1, String s2) {
262     String[] keys1 = s1.split(" ");
263     String[] keys2 = s2.split(" ");
264     int[] c1 = new int[4];
265     int[] c2 = new int[4];
266 
267     for (int i = 0; i < 4; i++) {
268       c1[i] = parseInt(keys1[i]);
269       c2[i] = parseInt(keys2[i]);
270     }
271 
272     if (c1[2] < c2[2]) {
273       return -1;
274     } else if (c1[2] > c2[2]) {
275       return 1;
276     } else if (c1[0] < c2[0]) {
277       return -1;
278     } else if (c1[0] > c2[0]) {
279       return 1;
280     } else if (c1[1] < c2[1]) {
281       return -1;
282     } else if (c1[1] > c2[1]) {
283       return 1;
284     } else if (c1[3] < c2[3]) {
285       return -1;
286     } else if (c1[3] > c2[3]) {
287       return 1;
288     }
289 
290     return 0;
291   }
292 
293   /**
294    * {@inheritDoc}
295    *
296    * <p>Override the default <code>equals</code> method.
297    *
298    * @since 1.0
299    */
300   @Override
301   public boolean equals(Object o) {
302     if (this == o) {
303       return true;
304     }
305     if (o == null || getClass() != o.getClass()) {
306       return false;
307     }
308     ImproperTorsionType improperTorsionType = (ImproperTorsionType) o;
309     return Arrays.equals(atomClasses, improperTorsionType.atomClasses);
310   }
311 
312   /**
313    * {@inheritDoc}
314    *
315    * <p>Implementation of the <code>hashCode</code> method.
316    *
317    * @since 1.0
318    */
319   @Override
320   public int hashCode() {
321     return Arrays.hashCode(atomClasses);
322   }
323 
324   /**
325    * incrementClasses
326    *
327    * @param increment The increment to add to the atom classes.
328    */
329   public void incrementClasses(int increment) {
330     for (int i = 0; i < atomClasses.length; i++) {
331       if (atomClasses[i] != 0) {
332         atomClasses[i] += increment;
333       }
334     }
335     setKey(sortKey(atomClasses));
336   }
337 
338   /**
339    * Check if this Improper Torsion Type is defined by 1 or more atom classes equal to zero.
340    *
341    * @return True if there are no zero "wildcard" atom classes for this type.
342    */
343   public boolean noZeroClasses() {
344     return atomClasses[0] != 0 && atomClasses[1] != 0 && atomClasses[3] != 0;
345   }
346 
347   /**
348    * Remap new atom classes to known internal ones.
349    *
350    * @param typeMap a lookup between new atom types and known atom types.
351    */
352   public void patchClasses(HashMap<AtomType, AtomType> typeMap) {
353     int count = 0;
354     for (AtomType newType : typeMap.keySet()) {
355       for (int atomClass : atomClasses) {
356         if (atomClass == newType.atomClass) {
357           count++;
358         }
359       }
360     }
361     if (count > 0 && count < atomClasses.length) {
362       for (AtomType newType : typeMap.keySet()) {
363         for (int i = 0; i < atomClasses.length; i++) {
364           if (atomClasses[i] == newType.atomClass) {
365             AtomType knownType = typeMap.get(newType);
366             atomClasses[i] = knownType.atomClass;
367           }
368         }
369       }
370       setKey(sortKey(atomClasses));
371     }
372   }
373 
374   /**
375    * {@inheritDoc}
376    *
377    * <p>Nicely formatted Torsion angle.
378    *
379    * @since 1.0
380    */
381   @Override
382   public String toString() {
383     StringBuilder imptorsBuffer = new StringBuilder("imptors");
384     for (int i : atomClasses) {
385       imptorsBuffer.append(String.format(" %5d", i));
386     }
387     imptorsBuffer.append(String.format(" %7.3f %7.3f %1d", k, phase, periodicity));
388 
389     return imptorsBuffer.toString();
390   }
391 }