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