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