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 javax.annotation.Nullable;
45  import java.util.Arrays;
46  import java.util.Comparator;
47  import java.util.HashMap;
48  import java.util.Map;
49  import java.util.logging.Level;
50  import java.util.logging.Logger;
51  
52  import static ffx.potential.parameters.ForceField.ForceFieldType.PITORS;
53  import static ffx.utilities.Constants.KCAL_TO_KJ;
54  import static ffx.utilities.PropertyGroup.EnergyUnitConversion;
55  import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
56  import static java.lang.Double.parseDouble;
57  import static java.lang.Integer.parseInt;
58  import static java.lang.String.format;
59  import static java.lang.String.valueOf;
60  
61  /**
62   * The PiOrbitalTorsionType class defines a Pi-Orbital Torsion energy term.
63   *
64   * @author Michael J. Schnieders
65   * @since 1.0
66   */
67  @FFXProperty(name = "pitors", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
68      [2 integers and 1 real]
69      Provides the values for a single pi-orbital torsional angle potential parameter.
70      The two integer modifiers give the atom class numbers for the atoms involved in the central bond of the torsional angle to be parameterized.
71      The real modifier gives the value of the 2-fold Fourier amplitude for the torsional angle between p-orbitals centered on the defined bond atom classes.
72      The default units for the stretch-torsion force constant can be controlled via the pitorsunit keyword.
73      """)
74  public final class PiOrbitalTorsionType extends BaseType implements Comparator<String> {
75  
76    /**
77     * A Logger for the PiTorsionType class.
78     */
79    private static final Logger logger = Logger.getLogger(PiOrbitalTorsionType.class.getName());
80  
81    public static final double DEFAULT_PITORS_UNIT = 1.0;
82  
83    /**
84     * Convert Pi-Torsion energy to kcal/mole.
85     */
86    @FFXProperty(name = "pitorsunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
87        Sets the scale factor needed to convert the energy value computed by the pi-orbital torsional angle potential into units of kcal/mole.
88        The correct value is force field dependent and typically provided in the header of the master force field parameter file.
89        """)
90    public double piTorsUnit = DEFAULT_PITORS_UNIT;
91  
92    /**
93     * Atom classes that form this Pi-Torsion.
94     */
95    public final int[] atomClasses;
96  
97    /**
98     * Force constant.
99     */
100   public double forceConstant;
101 
102   /**
103    * PiTorsionType Constructor.
104    *
105    * @param atomClasses   int[]
106    * @param forceConstant double
107    */
108   public PiOrbitalTorsionType(int[] atomClasses, double forceConstant) {
109     super(PITORS, sortKey(atomClasses));
110     this.atomClasses = atomClasses;
111     this.forceConstant = forceConstant;
112   }
113 
114   /**
115    * Average two PiTorsionType instances. The atom classes that define the new type must be
116    * supplied.
117    *
118    * @param piOrbitalTorsionType1 the first {@link PiOrbitalTorsionType} object.
119    * @param piOrbitalTorsionType2 the second {@link PiOrbitalTorsionType} object.
120    * @param atomClasses           the atom classes that define the new type.
121    * @return a {@link PiOrbitalTorsionType} object.
122    */
123   public static PiOrbitalTorsionType average(@Nullable PiOrbitalTorsionType piOrbitalTorsionType1,
124                                              @Nullable PiOrbitalTorsionType piOrbitalTorsionType2,
125                                              @Nullable int[] atomClasses) {
126     if (piOrbitalTorsionType1 == null || piOrbitalTorsionType2 == null || atomClasses == null) {
127       return null;
128     }
129 
130     double forceConstant =
131         (piOrbitalTorsionType1.forceConstant + piOrbitalTorsionType2.forceConstant) / 2.0;
132 
133     return new PiOrbitalTorsionType(atomClasses, forceConstant);
134   }
135 
136   /**
137    * Construct a PiTorsionType from an input string.
138    *
139    * @param input  The overall input String.
140    * @param tokens The input String tokenized.
141    * @return a PiTorsionType instance.
142    */
143   public static PiOrbitalTorsionType parse(String input, String[] tokens) {
144     if (tokens.length < 4) {
145       logger.log(Level.WARNING, "Invalid PITORS type:\n{0}", input);
146     } else {
147       try {
148         int[] atomClasses = new int[2];
149         atomClasses[0] = parseInt(tokens[1]);
150         atomClasses[1] = parseInt(tokens[2]);
151         double forceConstant = parseDouble(tokens[3]);
152         return new PiOrbitalTorsionType(atomClasses, forceConstant);
153       } catch (NumberFormatException e) {
154         String message = "Exception parsing PITORS type:\n" + input + "\n";
155         logger.log(Level.SEVERE, message, e);
156       }
157     }
158     return null;
159   }
160 
161   /**
162    * This method sorts the atom classes as: min, max
163    *
164    * @param c atomClasses
165    * @return lookup key
166    */
167   public static String sortKey(int[] c) {
168     if (c == null || c.length != 2) {
169       return null;
170     }
171 
172     int temp;
173     if (c[1] <= c[0]) {
174       temp = c[1];
175       c[1] = c[0];
176       c[0] = temp;
177     }
178     return c[0] + " " + c[1];
179   }
180 
181   /**
182    * {@inheritDoc}
183    */
184   @Override
185   public int compare(String s1, String s2) {
186     String[] keys1 = s1.split(" ");
187     String[] keys2 = s2.split(" ");
188 
189     for (int i = 0; i < 2; i++) {
190       int c1 = parseInt(keys1[i]);
191       int c2 = parseInt(keys2[i]);
192       if (c1 < c2) {
193         return -1;
194       } else if (c1 > c2) {
195         return 1;
196       }
197     }
198     return 0;
199   }
200 
201   /**
202    * {@inheritDoc}
203    */
204   @Override
205   public boolean equals(Object o) {
206     if (this == o) {
207       return true;
208     }
209     if (o == null || getClass() != o.getClass()) {
210       return false;
211     }
212     PiOrbitalTorsionType piOrbitalTorsionType = (PiOrbitalTorsionType) o;
213     return Arrays.equals(atomClasses, piOrbitalTorsionType.atomClasses);
214   }
215 
216   /**
217    * {@inheritDoc}
218    */
219   @Override
220   public int hashCode() {
221     return Arrays.hashCode(atomClasses);
222   }
223 
224   /**
225    * incrementClasses
226    *
227    * @param increment The increment to add to the atom classes.
228    */
229   public void incrementClasses(int increment) {
230     for (int i = 0; i < atomClasses.length; i++) {
231       atomClasses[i] += increment;
232     }
233     setKey(sortKey(atomClasses));
234   }
235 
236   /**
237    * Remap new atom classes to known internal ones.
238    *
239    * @param typeMap a lookup between new atom types and known atom types.
240    * @return a {@link PiOrbitalTorsionType} object.
241    */
242   public PiOrbitalTorsionType patchClasses(HashMap<AtomType, AtomType> typeMap) {
243     int count = 0;
244     int len = atomClasses.length;
245 
246     // Look for new PiTorsions that contain 1 mapped atom classes.
247     for (AtomType newType : typeMap.keySet()) {
248 
249       for (int atomClass : atomClasses) {
250         if (atomClass == newType.atomClass) {
251           count++;
252         }
253       }
254     }
255 
256     // If found, create a new PiTorsion that bridges to known classes.
257     if (count == 1) {
258       int[] newClasses = Arrays.copyOf(atomClasses, len);
259       for (AtomType newType : typeMap.keySet()) {
260         for (int i = 0; i < len; i++) {
261           if (atomClasses[i] == newType.atomClass) {
262             AtomType knownType = typeMap.get(newType);
263             newClasses[i] = knownType.atomClass;
264           }
265         }
266       }
267       return new PiOrbitalTorsionType(newClasses, forceConstant);
268     }
269     return null;
270   }
271 
272   /**
273    * {@inheritDoc}
274    *
275    * <p>Nicely formatted Pi-Torsion type.
276    */
277   @Override
278   public String toString() {
279     return format("pitors  %5d  %5d  %4.2f", atomClasses[0], atomClasses[1], forceConstant);
280   }
281 
282   /**
283    * Create an AmoebaPiTorsionForce Element.
284    *
285    * @param doc        the Document instance.
286    * @param forceField the ForceField instance to grab constants from.
287    * @return the AmoebaPiTorsionForce Element.
288    */
289   public static Element getXMLElement(Document doc, ForceField forceField) {
290     Map<String, PiOrbitalTorsionType> types = forceField.getPiOrbitalTorsionTypes();
291     if (!types.values().isEmpty()) {
292       Element node = doc.createElement("AmoebaPiTorsionForce");
293       node.setAttribute("piTorsionUnit", valueOf(forceField.getDouble("pitorsunit", DEFAULT_PITORS_UNIT)));
294       for (PiOrbitalTorsionType piOrbitalTorsionType : types.values()) {
295         node.appendChild(piOrbitalTorsionType.toXML(doc));
296       }
297       return node;
298     }
299     return null;
300   }
301 
302   /**
303    * Write PiOrbitalTorsionType to OpenMM XML format.
304    *
305    * @param doc the Document instance.
306    * @return the PiTorsion Element.
307    */
308   public Element toXML(Document doc) {
309     Element node = doc.createElement("PiTorsion");
310     node.setAttribute("class1", format("%d", atomClasses[0]));
311     node.setAttribute("class2", format("%d", atomClasses[1]));
312     // OpenMM has the piTorsUnit hard coded to 1.0 (the default).
313     node.setAttribute("k", format("%f", forceConstant * KCAL_TO_KJ));
314     return node;
315   }
316 
317 }