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.UREYBRAD;
52  import static ffx.utilities.Constants.ANG_TO_NM;
53  import static ffx.utilities.Constants.KCAL_TO_KJ;
54  import static ffx.utilities.PropertyGroup.EnergyUnitConversion;
55  import static ffx.utilities.PropertyGroup.LocalGeometryFunctionalForm;
56  import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
57  import static java.lang.Double.parseDouble;
58  import static java.lang.Integer.parseInt;
59  import static java.lang.String.format;
60  import static java.lang.String.valueOf;
61  
62  /**
63   * The UreyBradleyType class defines one harmonic UreyBradley cross term.
64   *
65   * @author Michael J. Schnieders
66   * @since 1.0
67   */
68  @FFXProperty(name = "ureybrad", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
69      [3 integers and 2 reals]
70      Provides the values for a single Urey-Bradley cross term potential parameter.
71      The integer modifiers give the atom class numbers for the three kinds of atoms
72      involved in the angle for which a Urey-Bradley term is to be defined.
73      The real number modifiers give the force constant value for the term and the target value for the 1-3 distance in Angstroms.
74      The default units for the force constant are kcal/mole/Ang^2, but this can be controlled via the ureyunit keyword
75      """)
76  public final class UreyBradleyType extends BaseType implements Comparator<String> {
77  
78    /**
79     * Default conversion Urey-Bradley stretch energy to kcal/mole.
80     */
81    public static final double DEFAULT_UREY_UNIT = 1.0;
82    /**
83     * Default cubic coefficient in Urey-Bradley stretch potential.
84     */
85    public static final double DEFAULT_UREY_CUBIC = 0.0;
86    /**
87     * Default quartic coefficient in Urey-Bradley stretch potential.
88     */
89    public static final double DEFAULT_UREY_QUARTIC = 0.0;
90  
91    /**
92     * Convert Urey-Bradley stretch energy to kcal/mole.
93     */
94    @FFXProperty(name = "ureyunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
95        Sets the scale factor needed to convert the energy value computed by the Urey-Bradley potential into units of kcal/mole.
96        The correct value is force field dependent and typically provided in the header of the master force field parameter file.
97        """)
98    public double ureyUnit = DEFAULT_UREY_UNIT;
99  
100   /**
101    * Cubic coefficient in bond stretch potential.
102    */
103   @FFXProperty(name = "urey-cubic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
104       Sets the value of the cubic term in the Taylor series expansion form of the Urey-Bradley potential energy.
105       The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
106       The default value in the absence of the urey-cubic keyword is zero; i.e., the cubic Urey-Bradley term is omitted.
107       """)
108   public double cubic = DEFAULT_UREY_CUBIC;
109 
110   /**
111    * Quartic coefficient in bond stretch potential.
112    */
113   @FFXProperty(name = "urey-quartic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
114       Sets the value of the quartic term in the Taylor series expansion form of the Urey-Bradley potential energy.
115       The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
116       The default value in the absence of the urey-quartic keyword is zero; i.e., the quartic Urey-Bradley term is omitted.
117       """)
118   public double quartic = DEFAULT_UREY_QUARTIC;
119 
120   /**
121    * A Logger for the UreyBradleyType class.
122    */
123   private static final Logger logger = Logger.getLogger(UreyBradleyType.class.getName());
124   /**
125    * Atom classes that form this Urey-Bradley cross term.
126    */
127   public final int[] atomClasses;
128   /**
129    * Force constant (Kcal/mole/angstroms^2).
130    */
131   public final double forceConstant;
132   /**
133    * Equilibrium 1-3 separation (Angstroms).
134    */
135   public final double distance;
136 
137   /**
138    * UreyBradleyType constructor.
139    *
140    * @param atomClasses   Atom classes.
141    * @param forceConstant Force constant (Kcal/mole/angstroms^2).
142    * @param distance      Equilibrium 1-3 separation (Angstroms).
143    */
144   public UreyBradleyType(int[] atomClasses, double forceConstant, double distance) {
145     super(UREYBRAD, sortKey(atomClasses));
146     this.atomClasses = atomClasses;
147     this.forceConstant = forceConstant;
148     this.distance = distance;
149   }
150 
151   /**
152    * average.
153    *
154    * @param ureyBradleyType1 a {@link ffx.potential.parameters.UreyBradleyType} object.
155    * @param ureyBradleyType2 a {@link ffx.potential.parameters.UreyBradleyType} object.
156    * @param atomClasses      an array of {@link int} objects.
157    * @return a {@link ffx.potential.parameters.UreyBradleyType} object.
158    */
159   public static UreyBradleyType average(UreyBradleyType ureyBradleyType1,
160                                         UreyBradleyType ureyBradleyType2, int[] atomClasses) {
161     if (ureyBradleyType1 == null || ureyBradleyType2 == null || atomClasses == null) {
162       return null;
163     }
164 
165     double forceConstant = (ureyBradleyType1.forceConstant + ureyBradleyType2.forceConstant) / 2.0;
166     double distance = (ureyBradleyType1.distance + ureyBradleyType2.distance) / 2.0;
167 
168     return new UreyBradleyType(atomClasses, forceConstant, distance);
169   }
170 
171   /**
172    * Construct a UreyBradleyType from an input string.
173    *
174    * @param input  The overall input String.
175    * @param tokens The input String tokenized.
176    * @return a UreyBradleyType instance.
177    */
178   public static UreyBradleyType parse(String input, String[] tokens) {
179     if (tokens.length < 5) {
180       logger.log(Level.WARNING, "Invalid UREYBRAD type:\n{0}", input);
181     } else {
182       try {
183         int[] atomClasses = new int[3];
184         atomClasses[0] = parseInt(tokens[1]);
185         atomClasses[1] = parseInt(tokens[2]);
186         atomClasses[2] = parseInt(tokens[3]);
187         double forceConstant = parseDouble(tokens[4]);
188         double distance = parseDouble(tokens[5]);
189         return new UreyBradleyType(atomClasses, forceConstant, distance);
190       } catch (NumberFormatException e) {
191         String message = "Exception parsing UREYBRAD type:\n" + input + "\n";
192         logger.log(Level.SEVERE, message, e);
193       }
194     }
195     return null;
196   }
197 
198   /**
199    * This method sorts the atom classes as: min, c[1], max
200    *
201    * @param c atomClasses
202    * @return lookup key
203    */
204   public static String sortKey(int[] c) {
205     if (c == null || c.length != 3) {
206       return null;
207     }
208     if (c[0] > c[2]) {
209       int temp = c[0];
210       c[0] = c[2];
211       c[2] = temp;
212     }
213 
214     return c[0] + " " + c[1] + " " + c[2];
215   }
216 
217   /**
218    * {@inheritDoc}
219    */
220   @Override
221   public int compare(String key1, String key2) {
222     String[] keys1 = key1.split(" ");
223     String[] keys2 = key2.split(" ");
224     int[] c1 = new int[3];
225     int[] c2 = new int[3];
226     for (int i = 0; i < 3; i++) {
227       c1[i] = parseInt(keys1[i]);
228       c2[i] = parseInt(keys2[i]);
229     }
230     if (c1[1] < c2[1]) {
231       return -1;
232     } else if (c1[1] > c2[1]) {
233       return 1;
234     } else if (c1[0] < c2[0]) {
235       return -1;
236     } else if (c1[0] > c2[0]) {
237       return 1;
238     } else if (c1[2] < c2[2]) {
239       return -1;
240     } else if (c1[2] > c2[2]) {
241       return 1;
242     }
243     return 0;
244   }
245 
246   /**
247    * {@inheritDoc}
248    */
249   @Override
250   public boolean equals(Object o) {
251     if (this == o) {
252       return true;
253     }
254     if (o == null || getClass() != o.getClass()) {
255       return false;
256     }
257     UreyBradleyType ureyBradleyType = (UreyBradleyType) o;
258     return Arrays.equals(atomClasses, ureyBradleyType.atomClasses);
259   }
260 
261   /**
262    * {@inheritDoc}
263    */
264   @Override
265   public int hashCode() {
266     return Arrays.hashCode(atomClasses);
267   }
268 
269   /**
270    * Increment the atom classes by a specified amount.
271    *
272    * @param increment The increment to apply to the atom classes.
273    */
274   public void incrementClasses(int increment) {
275     for (int i = 0; i < atomClasses.length; i++) {
276       atomClasses[i] += increment;
277     }
278     setKey(sortKey(atomClasses));
279   }
280 
281   /**
282    * Remap new atom classes to known internal ones.
283    *
284    * @param typeMap a lookup between new atom types and known atom types.
285    */
286   public void patchClasses(HashMap<AtomType, AtomType> typeMap) {
287     int count = 0;
288     for (AtomType newType : typeMap.keySet()) {
289       for (int atomClass : atomClasses) {
290         if (atomClass == newType.atomClass) {
291           count++;
292         }
293       }
294     }
295     if (count > 0 && count < atomClasses.length) {
296       for (AtomType newType : typeMap.keySet()) {
297         for (int i = 0; i < atomClasses.length; i++) {
298           if (atomClasses[i] == newType.atomClass) {
299             AtomType knownType = typeMap.get(newType);
300             atomClasses[i] = knownType.atomClass;
301           }
302         }
303       }
304       setKey(sortKey(atomClasses));
305     }
306   }
307 
308   /**
309    * {@inheritDoc}
310    *
311    * <p>Nicely formatted Urey-Bradley string.
312    */
313   @Override
314   public String toString() {
315     return format("ureybrad  %5d  %5d  %5d  %6.2f  %7.4f", atomClasses[0], atomClasses[1],
316         atomClasses[2], forceConstant, distance);
317   }
318 
319   /**
320    * Create an AmoebaUreyBradleyForce element.
321    *
322    * @param doc        the Document instance.
323    * @param forceField the ForceField to grab constants from.
324    * @return the AmoebaUreyBradleyForce element.
325    */
326   public static Element getXMLForce(Document doc, ForceField forceField) {
327     Map<String, UreyBradleyType> types = forceField.getUreyBradleyTypes();
328     if (!types.values().isEmpty()) {
329       Element node = doc.createElement("AmoebaUreyBradleyForce");
330       node.setAttribute("cubic", valueOf(forceField.getDouble("urey-cubic", DEFAULT_UREY_CUBIC)));
331       node.setAttribute("quartic", valueOf(forceField.getDouble("urey-quartic", DEFAULT_UREY_QUARTIC)));
332       for (UreyBradleyType ureyBradleyType : types.values()) {
333         node.appendChild(ureyBradleyType.toXML(doc));
334       }
335       return node;
336     }
337     return null;
338   }
339 
340   /**
341    * Write UreyBradleyType to OpenMM XML format.
342    */
343   public Element toXML(Document doc) {
344     Element node = doc.createElement("UreyBradley");
345     node.setAttribute("class1", format("%d", atomClasses[0]));
346     node.setAttribute("class2", format("%d", atomClasses[1]));
347     node.setAttribute("class3", format("%d", atomClasses[2]));
348     // Convert from kcal/mol/A^2 to KJ/mol/nm^2
349     node.setAttribute("k", format("%f", forceConstant * KCAL_TO_KJ / (ANG_TO_NM * ANG_TO_NM)));
350     // Convert from Angstroms to nm
351     node.setAttribute("d", format("%f", distance * ANG_TO_NM));
352     return node;
353   }
354 }