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.potential.bonded.Atom;
41  import ffx.utilities.FFXProperty;
42  import org.w3c.dom.Document;
43  import org.w3c.dom.Element;
44  
45  import java.util.Comparator;
46  import java.util.Map;
47  import java.util.Objects;
48  import java.util.logging.Level;
49  import java.util.logging.Logger;
50  
51  import static ffx.potential.parameters.ForceField.ForceFieldType.ATOM;
52  import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
53  import static java.lang.Double.parseDouble;
54  import static java.lang.Integer.parseInt;
55  import static java.lang.String.format;
56  import static org.apache.commons.math3.util.FastMath.abs;
57  
58  /**
59   * The AtomType class represents one molecular mechanics atom type.
60   *
61   * @author Michael J. Schnieders
62   * @since 1.0
63   */
64  @FFXProperty(name = "atom", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
65      [2 integers, name, quoted string, integer, real and integer]
66      Provides the values needed to define a single force field atom type.
67      The first two integer modifiers denote the atom type and class numbers.
68      If the type and class are identical, only a single integer value is required.
69      The next modifier is a three-character atom name, followed by an 24-character or less atom description contained in single quotes.
70      The next two modifiers are the atomic number and atomic mass.
71      The final integer modifier is the "valence" of the atom, defined as the expected number of attached or bonded atoms.
72      """)
73  public final class AtomType extends BaseType implements Comparator<String> {
74  
75    /**
76     * A Logger for the AngleType class.
77     */
78    private static final Logger logger = Logger.getLogger(AtomType.class.getName());
79    /**
80     * Short name (ie CH3/CH2 etc).
81     */
82    public final String name;
83    /**
84     * Description of the atom's bonding environment.
85     */
86    public final String environment;
87    /**
88     * Atomic Number.
89     */
90    public final int atomicNumber;
91    /**
92     * Atomic weight. "An atomic weight (relative atomic weight) of an element from a specified source
93     * is the ratio of the average atomicWeight per atom of the element to 1/12 of the atomicWeight of
94     * an atom of 12C"
95     */
96    public final double atomicWeight;
97    /**
98     * Valence number for this type.
99     */
100   public final int valence;
101   /**
102    * Atom type.
103    */
104   public int type;
105   /**
106    * Atom class.
107    */
108   public int atomClass;
109 
110   /**
111    * AtomType Constructor.
112    *
113    * @param type         int
114    * @param atomClass    int
115    * @param name         String
116    * @param environment  String
117    * @param atomicNumber int
118    * @param atomicWeight double
119    * @param valence      int
120    */
121   public AtomType(int type, int atomClass, String name, String environment, int atomicNumber,
122                   double atomicWeight, int valence) {
123     super(ATOM, Integer.toString(type));
124     this.type = type;
125     this.atomClass = atomClass;
126     this.name = name;
127     this.environment = environment;
128     this.atomicNumber = atomicNumber;
129     this.atomicWeight = atomicWeight;
130     this.valence = valence;
131   }
132 
133   /**
134    * Construct an AtomType from an input string.
135    *
136    * @param input  The overall input String.
137    * @param tokens The input String tokenized.
138    * @return an AtomType instance.
139    */
140   public static AtomType parse(String input, String[] tokens) {
141     if (tokens.length < 7) {
142       logger.log(Level.WARNING, "Invalid ATOM type:\n{0}", input);
143     } else {
144       try {
145         int index = 1;
146         // Atom Type
147         int type = parseInt(tokens[index++]);
148         // Atom Class
149         int atomClass;
150         // The following try/catch checks for one of the following two cases:
151         //
152         // NUMBER TYPE CLASS IDENTIFIER ... (example is OPLS-AA)
153         // vs.
154         // NUMBER TYPE IDENTIFIER ... (example is OPLS-UA)
155         //
156         // If there is no atom class, the exception will be caught
157         // and the atomClass field will remain equal to null.
158         try {
159           atomClass = parseInt(tokens[index]);
160           // If the parseInt succeeds, this force field has atom classes.
161           index++;
162         } catch (NumberFormatException e) {
163           // Some force fields do not use atom classes.
164           atomClass = -1;
165         }
166         // Name
167         String name = tokens[index].intern();
168         // The "environment" string may contain spaces,
169         // and is therefore surrounded in quotes located at "first" and
170         // "last".
171         int first = input.indexOf("\"");
172         int last = input.lastIndexOf("\"");
173         if (first >= last) {
174           logger.log(Level.WARNING, "Invalid ATOM type:\n{0}", input);
175           return null;
176         }
177         // Environment
178         String environment = input.substring(first, last + 1).intern();
179         // Shrink the tokens array to only include entries
180         // after the environment field.
181         tokens = input.substring(last + 1).trim().split(" +");
182         index = 0;
183         // Atomic Number
184         int atomicNumber = parseInt(tokens[index++]);
185         // Atomic Mass
186         double mass = parseDouble(tokens[index++]);
187         // Hybridization
188         int hybridization = parseInt(tokens[index]);
189 
190         AtomType atomType = new AtomType(type, atomClass, name, environment, atomicNumber, mass,
191             hybridization);
192         if (!checkAtomicNumberAndMass(atomicNumber, mass)) {
193           // Ignore united atom (UA) entries.
194           if (!environment.toUpperCase().contains("UA")) {
195             logger.warning(" Atomic number and weight do not agree:\n" + atomType);
196           }
197         }
198         return atomType;
199       } catch (NumberFormatException e) {
200         String message = "Exception parsing AtomType:\n" + input + "\n";
201         logger.log(Level.SEVERE, message, e);
202       }
203     }
204     return null;
205   }
206 
207   /**
208    * {@inheritDoc}
209    */
210   @Override
211   public int compare(String s1, String s2) {
212     int t1 = parseInt(s1);
213     int t2 = parseInt(s2);
214     return Integer.compare(t1, t2);
215   }
216 
217   /**
218    * {@inheritDoc}
219    */
220   @Override
221   public boolean equals(Object o) {
222     if (this == o) {
223       return true;
224     }
225     if (o == null || getClass() != o.getClass()) {
226       return false;
227     }
228     AtomType atomType = (AtomType) o;
229     return atomType.type == this.type;
230   }
231 
232   /**
233    * {@inheritDoc}
234    */
235   @Override
236   public int hashCode() {
237     return Objects.hash(type);
238   }
239 
240   /**
241    * {@inheritDoc}
242    *
243    * <p>Nicely formatted atom type string.
244    */
245   @Override
246   public String toString() {
247     String s;
248     if (atomClass >= 0) {
249       s = format("atom  %5d  %5d  %-4s  %-25s  %3d  %8.4f  %d", type, atomClass, name, environment,
250           atomicNumber, atomicWeight, valence);
251     } else {
252       s = format("atom  %5d  %-4s  %-25s  %3d  %8.4f  %d", type, name, environment, atomicNumber,
253           atomicWeight, valence);
254     }
255     return s;
256   }
257 
258   /**
259    * Create an AtomType Element.
260    *
261    * @param doc        the Document instance.
262    * @param forceField the ForceField to grab constants from.
263    * @return the AtomType element.
264    */
265   public static Element getXMLAtomTypes(Document doc, ForceField forceField) {
266     Element node = doc.createElement("AtomTypes");
267     Map<String, AtomType> types = forceField.getAtomTypes();
268     for (AtomType atomType : types.values()) {
269       node.appendChild(atomType.toXML(doc));
270     }
271     return node;
272   }
273 
274   /**
275    * Write AtomType to OpenMM XML format.
276    */
277   public Element toXML(Document doc) {
278     Element node = doc.createElement("Type");
279     node.setAttribute("name", format("%d", type));
280     node.setAttribute("class", format("%d", atomClass));
281     if (atomicNumber >= 1) {
282       node.setAttribute("element", format("%s", Atom.ElementSymbol.values()[atomicNumber - 1]));
283     } else {
284       // Handle force fields with dummy atoms that use atomic number 0.
285       node.setAttribute("element", "");
286     }
287     node.setAttribute("mass", format("%.3f", atomicWeight));
288     return node;
289   }
290 
291   /**
292    * incrementClassAndType
293    *
294    * @param classIncrement The value to increment the atom class by.
295    * @param typeIncrement  The value to increment the atom type by.
296    */
297   void incrementClassAndType(int classIncrement, int typeIncrement) {
298     atomClass += classIncrement;
299     type += typeIncrement;
300     setKey(Integer.toString(type));
301   }
302 
303   /**
304    * Check if the supplied atomic mass is within 0.1 AMU of the IUPAC value for the given atomic
305    * number.
306    * <p>
307    * For atomic numbers outside the range 1 to 118, true always is returned.
308    *
309    * @param atomicNumber The atomic number.
310    * @param mass         The atomic mass.
311    * @return True if the given mass is within the given tolerance of the IUPAC value for the atomic
312    * number.
313    */
314   public static boolean checkAtomicNumberAndMass(int atomicNumber, double mass) {
315     return checkAtomicNumberAndMass(atomicNumber, mass, 0.1);
316   }
317 
318   /**
319    * Check if the supplied atomic mass is within the supplied tolerance (in AMU) of the IUPAC value
320    * for the given atomic number.
321    * <p>
322    * For atomic numbers outside the range 1 to 118, true always is returned.
323    *
324    * @param atomicNumber The atomic number.
325    * @param mass         The atomic mass.
326    * @param tolerance    The error tolerance in AMU.
327    * @return True if the given mass is within the given tolerance of the IUPAC value for the atomic
328    * number.
329    */
330   public static boolean checkAtomicNumberAndMass(int atomicNumber, double mass, double tolerance) {
331     // Ignore atomic numbers outside the range 1 to 118.
332     if (atomicNumber == 0 || atomicNumber >= atomicMass.length) {
333       return true;
334     }
335 
336     double expected = atomicMass[atomicNumber - 1];
337     return abs(expected - mass) < tolerance;
338   }
339 
340   /**
341    * <a href="https://iupac.qmul.ac.uk/AtWt">IUPAC Commission</a> on Isotopic Abundances and Atomic
342    * Weights.
343    * Retrieved on 1/24/22.
344    */
345   public static final double[] atomicMass = { /* H Hydrogen */  1.008,
346       /* 2 He Helium */ 4.002,
347       /* 3 Li Lithium */ 6.94,
348       /* 4 Be Beryllium */ 9.012,
349       /* 5 B Boron */ 10.81,
350       /* 6 C Carbon */ 12.011,
351       /* 7 N Nitrogen */ 14.007,
352       /* 8 O Oxygen */ 15.999,
353       /* 9 F Fluorine */ 18.998,
354       /* 10 Ne Neon */ 20.1797,
355       /* 11 Na Sodium */ 22.989,
356       /* 12 Mg Magnesium */ 24.305,
357       /* 13 Al Aluminium */ 26.981,
358       /* 14 Si Silicon */ 28.085,
359       /* 15 P Phosphorus */ 30.973,
360       /* 16 S Sulfur */ 32.06,
361       /* 17 Cl Chlorine */ 35.45,
362       /* 18 Ar Argon */ 39.948,
363       /* 19 K Potassium */ 39.0983,
364       /* 20 Ca Calcium */ 40.078,
365       /* 21 Sc Scandium */ 44.955,
366       /* 22 Ti Titanium */ 47.867,
367       /* 23 V Vanadium */ 50.9415,
368       /* 24 Cr Chromium */ 51.9961,
369       /* 25 Mn Manganese */ 54.938,
370       /* 26 Fe Iron */ 55.845,
371       /* 27 Co Cobalt */ 58.933,
372       /* 28 Ni Nickel */ 58.6934,
373       /* 29 Cu Copper */ 63.546,
374       /* 30 Zn Zinc */ 65.38,
375       /* 31 Ga Gallium */ 69.723,
376       /* 32 Ge Germanium */ 72.630,
377       /* 33 As Arsenic */ 74.921,
378       /* 34 Se Selenium */ 78.971,
379       /* 35 Br Bromine */ 79.904,
380       /* 36 Kr Krypton */ 83.798,
381       /* 37 Rb Rubidium */ 85.4678,
382       /* 38 Sr Strontium */ 87.62,
383       /* 39 Y Yttrium */ 88.905,
384       /* 40 Zr Zirconium */ 91.224,
385       /* 41 Nb Niobium */ 92.906,
386       /* 42 Mo Molybdenum */ 95.95,
387       /* 43 Tc Technetium */ 97.0,
388       /* 44 Ru Ruthenium */ 101.07,
389       /* 45 Rh Rhodium */ 102.905,
390       /* 46 Pd Palladium */ 106.42,
391       /* 47 Ag Silver */ 107.8682,
392       /* 48 Cd Cadmium */ 112.414,
393       /* 49 In Indium */ 114.818,
394       /* 50 Sn Tin */ 118.710,
395       /* 51 Sb Antimony */ 121.760,
396       /* 52 Te Tellurium */ 127.60,
397       /* 53 I Iodine */ 126.904,
398       /* 54 Xe Xenon */ 131.293,
399       /* 55 Cs Caesium */ 132.905,
400       /* 56 Ba Barium */ 137.327,
401       /* 57 La Lanthanum */ 138.905,
402       /* 58 Ce Cerium */ 140.116,
403       /* 59 Pr Praseodymium */ 140.907,
404       /* 60 Nd Neodymium */ 144.242,
405       /* 61 Pm Promethium */ 145.0,
406       /* 62 Sm Samarium */ 150.36,
407       /* 63 Eu Europium */ 151.964,
408       /* 64 Gd Gadolinium */ 157.25,
409       /* 65 Tb Terbium */ 158.925,
410       /* 66 Dy Dysprosium */ 162.500,
411       /* 67 Ho Holmium */ 164.930,
412       /* 68 Er Erbium */ 167.259,
413       /* 69 Tm Thulium */ 168.934,
414       /* 70 Yb Ytterbium */ 173.045,
415       /* 71 Lu Lutetium */ 174.9668,
416       /* 72 Hf Hafnium */ 178.486,
417       /* 73 Ta Tantalum */ 180.947,
418       /* 74 W Tungsten */ 183.84,
419       /* 75 Re Rhenium */ 186.207,
420       /* 76 Os Osmium */ 190.23,
421       /* 77 Ir Iridium */ 192.217,
422       /* 78 Pt Platinum */ 195.084,
423       /* 79 Au Gold */ 196.966,
424       /* 80 Hg Mercury */ 200.592,
425       /* 81 Tl Thallium */ 204.38,
426       /* 82 Pb Lead */ 207.2,
427       /* 83 Bi Bismuth */ 208.980,
428       /* 84 Po Polonium */ 209.0,
429       /* 85 At Astatine */ 210.0,
430       /* 86 Rn Radon */ 222.0,
431       /* 87 Fr Francium */ 223.0,
432       /* 88 Ra Radium */ 226.0,
433       /* 89 Ac Actinium */ 227.0,
434       /* 90 Th Thorium */ 232.0377,
435       /* 91 Pa Protactinium */ 231.035,
436       /* 92 U Uranium */ 238.028,
437       /* 93 Np Neptunium */ 237.0,
438       /* 94 Pu Plutonium */ 244.0,
439       /* 95 Am Americium */ 243.0,
440       /* 96 Cm Curium */ 247.0,
441       /* 97 Bk Berkelium */ 247.0,
442       /* 98 Cf Californium */ 251.0,
443       /* 99 Es Einsteinium */ 252.0,
444       /* 100 Fm Fermium */ 257.0,
445       /* 101 Md Mendelevium */ 258.0,
446       /* 102 No Nobelium */ 259.0,
447       /* 103 Lr Lawrencium */ 262.0,
448       /* 104 Rf Rutherfordium */ 267.0,
449       /* 105 Db Dubnium */ 270.0,
450       /* 106 Sg Seaborgium */ 269.0,
451       /* 107 Bh Bohrium */ 270.0,
452       /* 108 Hs Hassium */ 270.0,
453       /* 109 Mt Meitnerium */ 278.0,
454       /* 110 Ds Darmstadtium */ 281.0,
455       /* 111 Rg Roentgenium */ 281.0,
456       /* 112 Cn Copernicium */ 285.0,
457       /* 113 Nh Nihonium */ 286.0,
458       /* 114 Fl Flerovium */ 289.0,
459       /* 115 Mc Moscovium */ 289.0,
460       /* 116 Lv Livermorium */ 293.0,
461       /* 117 Ts Tennessine */ 293.0,
462       /* 118 Og Oganesson */ 294.0};
463 }