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.STRBND;
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 java.util.Arrays.copyOf;
46  import static org.apache.commons.math3.util.FastMath.PI;
47  
48  import ffx.utilities.FFXProperty;
49  
50  import java.util.Arrays;
51  import java.util.Comparator;
52  import java.util.HashMap;
53  import java.util.logging.Level;
54  import java.util.logging.Logger;
55  
56  /**
57   * The StretchBendType class defines one out-of-plane angle bending energy type.
58   *
59   * @author Michael J. Schnieders
60   * @since 1.0
61   */
62  @FFXProperty(name = "strbnd", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
63      [3 integers and 2 reals]
64      Provides the values for a single stretch-bend cross term potential parameter.
65      The integer modifiers give the atom class numbers for the three kinds of atoms involved in the angle which is to be defined.
66      The real number modifiers give the force constant values for the first bond (first two atom classes) with the angle, and the second bond with the angle, respectively.
67      The default units for the stretch-bend force constant are kcal/mole/Ang-radian, but this can be controlled via the strbndunit keyword.
68      """)
69  public final class StretchBendType extends BaseType implements Comparator<String> {
70  
71    /**
72     * Constant <code>units=PI / 180.0</code>
73     */
74    public static final double DEFAULT_STRBND_UNIT = PI / 180.0;
75  
76    @FFXProperty(name = "strbndunit", propertyGroup = EnergyUnitConversion, defaultValue = "(Pi/180)", description = """
77        Sets the scale factor needed to convert the energy value computed by the bond stretching-angle bending cross
78        term potential into units of kcal/mole. The correct value is force field dependent and typically provided
79        in the header of the master force field parameter file.
80        """)
81    public double strbndunit = DEFAULT_STRBND_UNIT;
82  
83    private static final Logger logger = Logger.getLogger(StretchBendType.class.getName());
84    /**
85     * Atom class for this stretch-bend type.
86     */
87    public final int[] atomClasses;
88    /**
89     * Force constants (Kcal/mole/Angstrom-Degrees).
90     */
91    public final double[] forceConstants;
92  
93    /**
94     * StretchBendType Constructor.
95     *
96     * @param atomClasses    int[]
97     * @param forceConstants double[]
98     */
99    public StretchBendType(int[] atomClasses, double[] forceConstants) {
100     super(STRBND, sortKey(copyOf(atomClasses, 3)));
101     // Sort the atom classes and force constants in tandem.
102     if (atomClasses[0] > atomClasses[2]) {
103       int temp = atomClasses[0];
104       double f = forceConstants[0];
105       atomClasses[0] = atomClasses[2];
106       forceConstants[0] = forceConstants[1];
107       atomClasses[2] = temp;
108       forceConstants[1] = f;
109     }
110     this.atomClasses = atomClasses;
111     this.forceConstants = forceConstants;
112   }
113 
114   /**
115    * average.
116    *
117    * @param stretchBendType1 a {@link ffx.potential.parameters.StretchBendType} object.
118    * @param stretchBendType2 a {@link ffx.potential.parameters.StretchBendType} object.
119    * @param atomClasses      an array of {@link int} objects.
120    * @return a {@link ffx.potential.parameters.StretchBendType} object.
121    */
122   public static StretchBendType average(StretchBendType stretchBendType1,
123                                         StretchBendType stretchBendType2, int[] atomClasses) {
124     if (stretchBendType1 == null || stretchBendType2 == null || atomClasses == null) {
125       return null;
126     }
127     int len = stretchBendType1.forceConstants.length;
128     if (len != stretchBendType2.forceConstants.length) {
129       return null;
130     }
131     double[] forceConstants = new double[len];
132     for (int i = 0; i < len; i++) {
133       forceConstants[i] =
134           (stretchBendType1.forceConstants[i] + stretchBendType2.forceConstants[i]) / 2.0;
135     }
136     return new StretchBendType(atomClasses, forceConstants);
137   }
138 
139   /**
140    * Construct a StretchBendType from an input string.
141    *
142    * @param input  The overall input String.
143    * @param tokens The input String tokenized.
144    * @return a StretchBendType instance.
145    */
146   public static StretchBendType parse(String input, String[] tokens) {
147     if (tokens.length < 6) {
148       logger.log(Level.WARNING, "Invalid STRBND type:\n{0}", input);
149     } else {
150       try {
151         int[] atomClasses = new int[3];
152         atomClasses[0] = parseInt(tokens[1]);
153         atomClasses[1] = parseInt(tokens[2]);
154         atomClasses[2] = parseInt(tokens[3]);
155         double[] forceConstants = new double[2];
156         forceConstants[0] = parseDouble(tokens[4]);
157         forceConstants[1] = parseDouble(tokens[5]);
158         return new StretchBendType(atomClasses, forceConstants);
159       } catch (NumberFormatException e) {
160         String message = "Exception parsing STRBND type:\n" + input + "\n";
161         logger.log(Level.SEVERE, message, e);
162       }
163     }
164     return null;
165   }
166 
167   /**
168    * This method sorts the atom classes as: min, c[1], max
169    *
170    * @param c atomClasses
171    * @return lookup key
172    */
173   public static String sortKey(int[] c) {
174     if (c == null || c.length != 3) {
175       return null;
176     }
177     if (c[0] > c[2]) {
178       int temp = c[0];
179       c[0] = c[2];
180       c[2] = temp;
181     }
182     return c[0] + " " + c[1] + " " + c[2];
183   }
184 
185   /**
186    * {@inheritDoc}
187    */
188   @Override
189   public int compare(String key1, String key2) {
190     String[] keys1 = key1.split(" ");
191     String[] keys2 = key2.split(" ");
192     int[] c1 = new int[3];
193     int[] c2 = new int[3];
194     for (int i = 0; i < 3; i++) {
195       c1[i] = parseInt(keys1[i]);
196       c2[i] = parseInt(keys2[i]);
197     }
198     if (c1[1] < c2[1]) {
199       return -1;
200     } else if (c1[1] > c2[1]) {
201       return 1;
202     } else if (c1[0] < c2[0]) {
203       return -1;
204     } else if (c1[0] > c2[0]) {
205       return 1;
206     } else if (c1[2] < c2[2]) {
207       return -1;
208     } else if (c1[2] > c2[2]) {
209       return 1;
210     }
211     return 0;
212   }
213 
214   /**
215    * {@inheritDoc}
216    */
217   @Override
218   public boolean equals(Object o) {
219     if (this == o) {
220       return true;
221     }
222     if (o == null || getClass() != o.getClass()) {
223       return false;
224     }
225     StretchBendType stretchBendType = (StretchBendType) o;
226     return Arrays.equals(atomClasses, stretchBendType.atomClasses);
227   }
228 
229   /**
230    * {@inheritDoc}
231    */
232   @Override
233   public int hashCode() {
234     return Arrays.hashCode(atomClasses);
235   }
236 
237   /**
238    * Increment the atom classes by a given value.
239    *
240    * @param increment The increment to apply to the atom classes.
241    */
242   public void incrementClasses(int increment) {
243     for (int i = 0; i < atomClasses.length; i++) {
244       atomClasses[i] += increment;
245     }
246     setKey(sortKey(atomClasses));
247   }
248 
249   /**
250    * Remap new atom classes to known internal ones.
251    *
252    * @param typeMap a lookup between new atom types and known atom types.
253    * @return a {@link ffx.potential.parameters.StretchBendType} object.
254    */
255   public StretchBendType patchClasses(HashMap<AtomType, AtomType> typeMap) {
256     int count = 0;
257     int len = atomClasses.length;
258 
259     // Check if this StetchBendType contain 1 or 2 mapped atom classes.
260     for (AtomType newType : typeMap.keySet()) {
261       for (int atomClass : atomClasses) {
262         if (atomClass == newType.atomClass) {
263           count++;
264         }
265       }
266     }
267 
268     // If found, create a new StetchBendType that bridges to known classes.
269     if (count == 1 || count == 2) {
270       int[] newClasses = Arrays.copyOf(atomClasses, len);
271       for (AtomType newType : typeMap.keySet()) {
272         for (int i = 0; i < len; i++) {
273           if (atomClasses[i] == newType.atomClass) {
274             AtomType knownType = typeMap.get(newType);
275             newClasses[i] = knownType.atomClass;
276           }
277         }
278       }
279 
280       return new StretchBendType(newClasses, forceConstants);
281     }
282     return null;
283   }
284 
285   /**
286    * {@inheritDoc}
287    *
288    * <p>Nicely formatted Stretch-Bend string.
289    */
290   @Override
291   public String toString() {
292     return String.format("strbnd  %5d  %5d  %5d  %6.2f  %6.2f", atomClasses[0], atomClasses[1],
293         atomClasses[2], forceConstants[0], forceConstants[1]);
294   }
295 }