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