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.parsers.OpenMMXmlFilter;
41  import ffx.utilities.FFXProperty;
42  import org.apache.commons.configuration2.CompositeConfiguration;
43  
44  import java.net.URL;
45  import java.util.ArrayList;
46  import java.util.Arrays;
47  import java.util.Collection;
48  import java.util.EnumMap;
49  import java.util.HashMap;
50  import java.util.List;
51  import java.util.Map;
52  import java.util.TreeMap;
53  import java.util.logging.Level;
54  import java.util.logging.Logger;
55  
56  import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
57  import static java.lang.String.format;
58  
59  /**
60   * The ForceField class organizes parameters for a molecular mechanics force field.
61   *
62   * @author Michael J. Schnieders
63   * @since 1.0
64   */
65  @FFXProperty(name = "forcefield", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
66      [name]
67      Provides a name for the force field to be used in the current calculation.
68      Its value is usually set in the master force field parameter file for the calculation
69      (see the PARAMETERS keyword) instead of in the property file.
70      """)
71  public class ForceField {
72  
73    private static final Logger logger = Logger.getLogger(ForceField.class.getName());
74    /**
75     * A map between a force field name and its internal parameter file.
76     */
77    private static final Map<ForceFieldName, URL> forceFields = new EnumMap<>(ForceFieldName.class);
78  
79    static {
80      ClassLoader cl = ForceField.class.getClassLoader();
81      String prefix = "ffx/potential/parameters/ff/";
82      for (ForceFieldName ff : ForceFieldName.values()) {
83        forceFields.put(ff, cl.getResource(prefix + ff));
84      }
85    }
86  
87    /**
88     * The CompositeConfiguration that contains key=value property pairs from a number of sources.
89     */
90    private final CompositeConfiguration properties;
91    private final Map<String, AngleType> angleTypes;
92    private final Map<String, AngleType> anglepTypes;
93    private final Map<String, AtomType> atomTypes;
94    private final Map<String, BioType> bioTypes;
95    private final Map<String, BondType> bondTypes;
96    private final Map<String, ChargeType> chargeTypes;
97    private final Map<String, MultipoleType> multipoleTypes;
98    private final Map<String, OutOfPlaneBendType> outOfPlaneBendTypes;
99    private final Map<String, PolarizeType> polarizeTypes;
100   private final Map<String, StretchBendType> stretchBendTypes;
101   private final Map<String, StretchTorsionType> stretchTorsionTypes;
102   private final Map<String, AngleTorsionType> angleTorsionTypes;
103   private final Map<String, PiOrbitalTorsionType> piOrbitalTorsionTypes;
104   private final Map<String, TorsionType> torsionTypes;
105   private final Map<String, TorsionType> improperTypes;
106   private final Map<String, ImproperTorsionType> imptorsTypes;
107   private final Map<String, SoluteType> soluteTypes;
108   private final Map<String, TorsionTorsionType> torsionTorsionTypes;
109   private final Map<String, UreyBradleyType> ureyBradleyTypes;
110   private final Map<String, VDWType> vanderWaalsTypes;
111   private final Map<String, VDWType> vanderWaals14Types;
112   private final Map<String, VDWPairType> vanderWaalsPairTypes;
113   private final Map<String, RelativeSolvationType> relativeSolvationTypes;
114   private final Map<ForceFieldType, Map<String, ? extends BaseType>> forceFieldTypes;
115   /**
116    * URL to the force field parameter file.
117    */
118   public URL forceFieldURL;
119 
120   /**
121    * ForceField Constructor.
122    *
123    * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
124    *                   object.
125    */
126   public ForceField(CompositeConfiguration properties) {
127     this.properties = properties;
128 
129     /*
130      Each force field "type" implements the "Comparator<String>" interface
131      so that passing an "empty" instance of the "type" to its TreeMap
132      constructor will keep the types sorted.
133     */
134     angleTypes = new TreeMap<>(new AngleType(new int[3], 0, new double[1], null));
135     anglepTypes = new TreeMap<>(new AngleType(new int[3], 0, new double[1], null, null));
136     atomTypes = new TreeMap<>(new AtomType(0, 0, null, null, 0, 0, 0));
137     bioTypes = new TreeMap<>(new BioType(0, null, null, 0, null));
138     bondTypes = new TreeMap<>(new BondType(new int[2], 0, 0, null));
139     chargeTypes = new TreeMap<>(new ChargeType(0, 0));
140     soluteTypes = new TreeMap<>(new SoluteType(0, 0.0, 0.0, 0.0));
141     multipoleTypes = new TreeMap<>(new MultipoleType(new double[10], null, null, false));
142     outOfPlaneBendTypes = new TreeMap<>(new OutOfPlaneBendType(new int[4], 0));
143     piOrbitalTorsionTypes = new TreeMap<>(new PiOrbitalTorsionType(new int[2], 0));
144     polarizeTypes = new TreeMap<>(new PolarizeType(0, 0, 0, 0, new int[1]));
145     stretchBendTypes = new TreeMap<>(new StretchBendType(new int[3], new double[1]));
146     stretchTorsionTypes = new TreeMap<>(new StretchTorsionType(new int[4], new double[1]));
147     angleTorsionTypes = new TreeMap<>(new AngleTorsionType(new int[4], new double[1]));
148     torsionTorsionTypes = new TreeMap<>();
149     torsionTypes = new TreeMap<>(
150         new TorsionType(new int[4], new double[1], new double[1], new int[1]));
151     improperTypes = new TreeMap<>(
152         new TorsionType(new int[4], new double[1], new double[1], new int[1]));
153     imptorsTypes = new TreeMap<>(new ImproperTorsionType(new int[4], 0.0, 0.0, 2));
154     ureyBradleyTypes = new TreeMap<>(new UreyBradleyType(new int[3], 0, 0));
155     vanderWaalsTypes = new TreeMap<>(new VDWType(0, 0, 0, 0));
156     vanderWaals14Types = new TreeMap<>(new VDWType(0, 0, 0, 0));
157     vanderWaalsPairTypes = new TreeMap<>(new VDWPairType(new int[2], 0, 0));
158     relativeSolvationTypes = new TreeMap<>(new RelativeSolvationType("", 0.0));
159 
160     forceFieldTypes = new EnumMap<>(ForceFieldType.class);
161     forceFieldTypes.put(ForceFieldType.ANGLE, angleTypes);
162     forceFieldTypes.put(ForceFieldType.ANGLEP, anglepTypes);
163     forceFieldTypes.put(ForceFieldType.ATOM, atomTypes);
164     forceFieldTypes.put(ForceFieldType.BOND, bondTypes);
165     forceFieldTypes.put(ForceFieldType.BIOTYPE, bioTypes);
166     forceFieldTypes.put(ForceFieldType.CHARGE, chargeTypes);
167     forceFieldTypes.put(ForceFieldType.SOLUTE, soluteTypes);
168     forceFieldTypes.put(ForceFieldType.OPBEND, outOfPlaneBendTypes);
169     forceFieldTypes.put(ForceFieldType.MULTIPOLE, multipoleTypes);
170     forceFieldTypes.put(ForceFieldType.PITORS, piOrbitalTorsionTypes);
171     forceFieldTypes.put(ForceFieldType.POLARIZE, polarizeTypes);
172     forceFieldTypes.put(ForceFieldType.STRBND, stretchBendTypes);
173     forceFieldTypes.put(ForceFieldType.STRTORS, stretchTorsionTypes);
174     forceFieldTypes.put(ForceFieldType.ANGTORS, angleTorsionTypes);
175     forceFieldTypes.put(ForceFieldType.TORSION, torsionTypes);
176     forceFieldTypes.put(ForceFieldType.IMPROPER, improperTypes);
177     forceFieldTypes.put(ForceFieldType.IMPTORS, imptorsTypes);
178     forceFieldTypes.put(ForceFieldType.TORTORS, torsionTorsionTypes);
179     forceFieldTypes.put(ForceFieldType.UREYBRAD, ureyBradleyTypes);
180     forceFieldTypes.put(ForceFieldType.VDW, vanderWaalsTypes);
181     forceFieldTypes.put(ForceFieldType.VDW14, vanderWaals14Types);
182     forceFieldTypes.put(ForceFieldType.VDWPR, vanderWaalsPairTypes);
183     forceFieldTypes.put(ForceFieldType.RELATIVESOLV, relativeSolvationTypes);
184 
185     trueImpliedBoolean("ELEC_LAMBDATERM", "GK_LAMBDATERM");
186     trueImpliedBoolean("LAMBDATERM", "VDW_LAMBDATERM", "ELEC_LAMBDATERM", "GK_LAMBDATERM");
187   }
188 
189   /**
190    * Get for the URL for the named force field.
191    *
192    * @param forceField a {@link ForceField.ForceFieldName} object.
193    * @return a {@link java.net.URL} object.
194    */
195   public static URL getForceFieldURL(ForceFieldName forceField) {
196     if (forceField == null) {
197       return null;
198     }
199     return forceFields.get(forceField);
200   }
201 
202   /**
203    * Check if a keyword is a force field type.
204    *
205    * @param keyword The keyword to check.
206    * @return True if the keyword is a force field type.
207    */
208   public static boolean isForceFieldType(String keyword) {
209     keyword = toEnumForm(keyword);
210     try {
211       ForceFieldType.valueOf(keyword);
212       return true;
213     } catch (Exception e) {
214       // Ignore.
215     }
216     return false;
217   }
218 
219   /**
220    * Enums are uppercase with underscores, but property files use lower case with dashes.
221    *
222    * @param key an input keyword
223    * @return the keyword in Enum form.
224    */
225   public static String toEnumForm(String key) {
226     if (key == null) {
227       return null;
228     }
229     return key.toUpperCase().replace("-", "_");
230   }
231 
232   /**
233    * Enums are uppercase with underscores, but property files use lower case with dashes.
234    *
235    * @param s an input Enum string
236    * @return the normalized keyword
237    */
238   public static String toPropertyForm(String s) {
239     if (s == null) {
240       return null;
241     }
242     return s.toLowerCase().replace("_", "-");
243   }
244 
245   /**
246    * Add an instance of a force field type. Force Field types are more complicated than simple
247    * Strings or doubles, in that they have multiple fields and may occur multiple times.
248    *
249    * @param <T>  ForceFieldType to add that extends BaseType
250    * @param type The ForceFieldType to add.
251    */
252   @SuppressWarnings("unchecked")
253   public <T extends BaseType> void addForceFieldType(T type) {
254     if (type == null) {
255       logger.info(" Null force field type ignored.");
256       return;
257     }
258 
259     Map<String, T> treeMap = (Map<String, T>) forceFieldTypes.get(type.forceFieldType);
260     if (treeMap == null) {
261       logger.log(Level.INFO, " Unrecognized force field type ignored {0}", type.forceFieldType);
262       type.print();
263       return;
264     }
265     if (treeMap.containsKey(type.key)) {
266       if (treeMap.get(type.key).toString().equalsIgnoreCase(type.toString())) {
267         // Ignore this type if it's identical to an existing type.
268         return;
269       }
270       logger.log(Level.WARNING,
271           " A force field entry of type {0} already exists with the key: {1}\n The (discarded) old entry: {2}\n The new entry            : {3}",
272           new Object[]{type.forceFieldType, type.key, treeMap.get(type.key).toString(),
273               type.toString()});
274     }
275     treeMap.put(type.key, type);
276   }
277 
278   /**
279    * Add a property from an external parameter file.
280    *
281    * @param property Property string.
282    * @param value    double
283    */
284   public void addProperty(String property, String value) {
285     if (property == null) {
286       return;
287     }
288     String key = toPropertyForm(property);
289     //        try {
290     //            String old = getString(key);
291     //            if (old.equalsIgnoreCase(value)) {
292     //                return;
293     //            }
294     //            logger.info(format("  Existing %s  %s", key, old));
295     //        } catch (Exception e) {
296     //            // Property does not exist yet.
297     //        } finally {
298     //            properties.addProperty(key, value);
299     //            logger.info(format("  Added    %s  %s", key, value));
300     //        }
301     properties.addProperty(key, value);
302   }
303 
304   /**
305    * Clear a property from the force field instance.
306    *
307    * @param property Property to clear.
308    */
309   public void clearProperty(String property) {
310     properties.clearProperty(property);
311   }
312 
313   /**
314    * Append a 2nd ForceField "patch" to the current ForceField. Note that only the force field types
315    * are appended; properties are ignored.
316    *
317    * @param patch The force field patch to append.
318    */
319   public void append(ForceField patch) {
320 
321     boolean renumber = patch.getBoolean("renumberPatch", true);
322     logger.info(format(" Renumbering Patch: %B", renumber));
323 
324     if (renumber) {
325       // Determine the highest current atom class, atom type and biotype index.
326       int classOffset = maxClass();
327       int typeOffset = maxType();
328       int bioTypeOffset = maxBioType();
329 
330       int minClass = patch.minClass();
331       int minType = patch.minType();
332       int minBioType = patch.minBioType();
333 
334       classOffset -= (minClass - 1);
335       typeOffset -= (minType - 1);
336       bioTypeOffset -= (minBioType - 1);
337 
338       patch.renumberForceField(classOffset, typeOffset, bioTypeOffset);
339     }
340 
341     for (AngleType angleType : patch.angleTypes.values()) {
342       angleTypes.put(angleType.getKey(), angleType);
343     }
344 
345     for (AngleType angleType : patch.anglepTypes.values()) {
346       anglepTypes.put(angleType.getKey(), angleType);
347     }
348 
349     for (AtomType atomType : patch.atomTypes.values()) {
350       atomTypes.put(atomType.getKey(), atomType);
351     }
352 
353     for (BioType bioType : patch.bioTypes.values()) {
354       bioTypes.put(bioType.getKey(), bioType);
355     }
356 
357     for (BondType bondType : patch.bondTypes.values()) {
358       bondTypes.put(bondType.getKey(), bondType);
359     }
360 
361     for (MultipoleType multipoleType : patch.multipoleTypes.values()) {
362       multipoleTypes.put(multipoleType.getKey(), multipoleType);
363     }
364 
365     for (OutOfPlaneBendType outOfPlaneBendType : patch.outOfPlaneBendTypes.values()) {
366       outOfPlaneBendTypes.put(outOfPlaneBendType.getKey(), outOfPlaneBendType);
367     }
368 
369     for (PiOrbitalTorsionType piOrbitalTorsionType : patch.piOrbitalTorsionTypes.values()) {
370       piOrbitalTorsionTypes.put(piOrbitalTorsionType.getKey(), piOrbitalTorsionType);
371     }
372 
373     for (PolarizeType polarizeType : patch.polarizeTypes.values()) {
374       polarizeTypes.put(polarizeType.getKey(), polarizeType);
375     }
376 
377     for (StretchBendType stretchBendType : patch.stretchBendTypes.values()) {
378       stretchBendTypes.put(stretchBendType.getKey(), stretchBendType);
379     }
380 
381     for (StretchTorsionType stretchTorsionType : patch.stretchTorsionTypes.values()) {
382       stretchTorsionTypes.put(stretchTorsionType.getKey(), stretchTorsionType);
383     }
384 
385     for (AngleTorsionType angleTorsionType : patch.angleTorsionTypes.values()) {
386       angleTorsionTypes.put(angleTorsionType.getKey(), angleTorsionType);
387     }
388 
389     for (TorsionTorsionType torsionTorsionType : patch.torsionTorsionTypes.values()) {
390       torsionTorsionTypes.put(torsionTorsionType.getKey(), torsionTorsionType);
391     }
392 
393     for (TorsionType torsionType : patch.torsionTypes.values()) {
394       torsionTypes.put(torsionType.getKey(), torsionType);
395     }
396 
397     for (TorsionType torsionType : patch.improperTypes.values()) {
398       torsionTypes.put(torsionType.getKey(), torsionType);
399     }
400 
401     for (ImproperTorsionType improperTorsionType : patch.imptorsTypes.values()) {
402       imptorsTypes.put(improperTorsionType.getKey(), improperTorsionType);
403     }
404 
405     for (UreyBradleyType ureyBradleyType : patch.ureyBradleyTypes.values()) {
406       ureyBradleyTypes.put(ureyBradleyType.getKey(), ureyBradleyType);
407     }
408 
409     for (VDWType vdwType : patch.vanderWaalsTypes.values()) {
410       vanderWaalsTypes.put(vdwType.getKey(), vdwType);
411     }
412 
413     for (VDWType vdwType : patch.vanderWaals14Types.values()) {
414       vanderWaals14Types.put(vdwType.getKey(), vdwType);
415     }
416 
417     for (VDWPairType vdwPairType : patch.vanderWaalsPairTypes.values()) {
418       vanderWaalsPairTypes.put(vdwPairType.getKey(), vdwPairType);
419     }
420 
421     for (SoluteType soluteType : patch.soluteTypes.values()) {
422       soluteTypes.put(soluteType.getKey(), soluteType);
423     }
424 
425     for (RelativeSolvationType rsType : patch.relativeSolvationTypes.values()) {
426       relativeSolvationTypes.put(rsType.getKey(), rsType);
427     }
428 
429     // Is this a modified residue patch?
430     String modres = patch.getString("MODRES", "false");
431     if (!modres.equalsIgnoreCase("false")) {
432       logger.info(" Adding modified residue patch.");
433       modifiedResidue(modres);
434     }
435   }
436 
437   /**
438    * getAngleTorsionType
439    *
440    * @param key a {@link java.lang.String} object.
441    * @return a {@link ffx.potential.parameters.AngleTorsionType} object.
442    */
443   public AngleTorsionType getAngleTorsionType(String key) {
444     AngleTorsionType angleTorsionType = angleTorsionTypes.get(key);
445     if (angleTorsionType != null) {
446       angleTorsionType.angtorunit = getDouble("ANGTORUNIT", AngleTorsionType.DEFAULT_ANGTOR_UNIT);
447     }
448     return angleTorsionType;
449   }
450 
451   /**
452    * Get AngleTorsionTypes.
453    *
454    * @return The AngleTorsionTypes.
455    */
456   public Map<String, AngleTorsionType> getAngleTorsionTypes() {
457     // Finalize keywords for AngleTorsionTypes.
458     for (String key : angleTorsionTypes.keySet()) {
459       getAngleTorsionType(key);
460     }
461     return angleTorsionTypes;
462   }
463 
464   /**
465    * getAngleType
466    *
467    * @param key a {@link java.lang.String} object.
468    * @return a {@link ffx.potential.parameters.AngleType} object.
469    */
470   public AngleType getAngleType(String key) {
471     AngleType angleType = angleTypes.get(key);
472     if (angleType == null) {
473       angleType = anglepTypes.get(key);
474     }
475     if (angleType != null) {
476       angleType.angleUnit = getDouble("ANGLEUNIT", AngleType.DEFAULT_ANGLE_UNIT);
477       angleType.cubic = getDouble("ANGLE-CUBIC", AngleType.DEFAULT_ANGLE_CUBIC);
478       angleType.quartic = getDouble("ANGLE-QUARTIC", AngleType.DEFAULT_ANGLE_QUARTIC);
479       angleType.pentic = getDouble("ANGLE-PENTIC", AngleType.DEFAULT_ANGLE_PENTIC);
480       angleType.sextic = getDouble("ANGLE-SEXTIC", AngleType.DEFAULT_ANGLE_SEXTIC);
481       double ridingScale = getDouble("RIDING_HYDROGEN_SCALE", 1.0);
482       if (ridingScale > 1.0 && hasHydrogenAtomClass(angleType.atomClasses)) {
483         angleType.angleUnit *= ridingScale;
484       }
485     }
486     return angleType;
487   }
488 
489   /**
490    * Get AngleTypes.
491    *
492    * @return The AngleTypes.
493    */
494   public Map<String, AngleType> getAngleTypes() {
495     // Finalize keywords for Angle Types.
496     for (String key : angleTypes.keySet()) {
497       getAngleType(key);
498     }
499     return angleTypes;
500   }
501 
502   public Map<String, AngleType> getAnglepTypes() {
503     // Finalize keywords for Anglep Types.
504     for (String key : anglepTypes.keySet()) {
505       getAngleType(key);
506     }
507     return anglepTypes;
508   }
509 
510   /**
511    * getAngleType
512    *
513    * @param a1 First AtomType.
514    * @param a2 Second AtomType.
515    * @param a3 Third AtomType.
516    * @return a {@link ffx.potential.parameters.AngleType} object.
517    */
518   public AngleType getAngleType(AtomType a1, AtomType a2, AtomType a3) {
519     int[] c = {a1.atomClass, a2.atomClass, a3.atomClass};
520     String key = AngleType.sortKey(c);
521     return getAngleType(key);
522   }
523 
524   /**
525    * getAtomType
526    *
527    * @param key a {@link java.lang.String} object.
528    * @return a {@link ffx.potential.parameters.AtomType} object.
529    */
530   public AtomType getAtomType(String key) {
531     return atomTypes.get(key);
532   }
533 
534   /**
535    * Get AtomTypes.
536    *
537    * @return the AtomTypes.
538    */
539   public Map<String, AtomType> getAtomTypes() {
540     return atomTypes;
541   }
542 
543   /**
544    * getAtomType
545    *
546    * @param moleculeName a {@link java.lang.String} object.
547    * @param atomName     a {@link java.lang.String} object.
548    * @return a {@link ffx.potential.parameters.AtomType} object.
549    */
550   public AtomType getAtomType(String moleculeName, String atomName) {
551     for (BioType bioType : bioTypes.values()) {
552       if (bioType.moleculeName.equalsIgnoreCase(moleculeName) && bioType.atomName.equalsIgnoreCase(
553           atomName)) {
554         String key = Integer.toString(bioType.atomType);
555         return atomTypes.get(key);
556       }
557     }
558     return null;
559   }
560 
561   /**
562    * Getter for the field <code>atomTypes</code>.
563    *
564    * @param atomType AtomType to find similar examples of.
565    * @return Similar atom types.
566    */
567   public List<AtomType> getSimilarAtomTypes(AtomType atomType) {
568     List<AtomType> types = new ArrayList<>();
569     for (AtomType type : atomTypes.values()) {
570       if (type.atomicNumber == atomType.atomicNumber && type.valence == atomType.valence) {
571         types.add(type);
572       }
573     }
574     return types;
575   }
576 
577   /**
578    * Getter for the field <code>atomTypes</code>.
579    *
580    * @param moleculeName a {@link java.lang.String} object.
581    * @return a {@link java.util.HashMap} object.
582    */
583   public HashMap<String, AtomType> getAtomTypes(String moleculeName) {
584     HashMap<String, AtomType> types = new HashMap<>();
585     for (BioType bioType : bioTypes.values()) {
586       if (bioType.moleculeName.equalsIgnoreCase(moleculeName)) {
587         String key = Integer.toString(bioType.atomType);
588         AtomType type = atomTypes.get(key);
589         types.put(bioType.atomName.toUpperCase(), type);
590       }
591     }
592     return types;
593   }
594 
595   /**
596    * getBioType.
597    *
598    * @param moleculeName a {@link java.lang.String} object.
599    * @param atomName     a {@link java.lang.String} object.
600    * @return a {@link ffx.potential.parameters.BioType} object.
601    */
602   public BioType getBioType(String moleculeName, String atomName) {
603     for (BioType bioType : bioTypes.values()) {
604       if (bioType.moleculeName.equalsIgnoreCase(moleculeName) && bioType.atomName.equalsIgnoreCase(
605           atomName)) {
606         return bioType;
607       }
608     }
609     return null;
610   }
611 
612   /**
613    * getBioType
614    *
615    * @param key a {@link java.lang.String} object.
616    * @return a {@link ffx.potential.parameters.BioType} object.
617    */
618   public BioType getBioType(String key) {
619     return bioTypes.get(key);
620   }
621 
622   /**
623    * getBioTypeMap.
624    *
625    * @return a {@link java.util.Map} object.
626    */
627   public Map<String, BioType> getBioTypeMap() {
628     return bioTypes;
629   }
630 
631   /**
632    * Determines if the given atom classes correspond to a hydrogen type.
633    *
634    * @param atomClasses an array of integers representing atom classes to be checked
635    * @return true if at least one of the atom classes corresponds to a hydrogen, false otherwise
636    */
637   private boolean hasHydrogenAtomClass(int[] atomClasses) {
638     for (AtomType type : atomTypes.values()) {
639       if (type.atomicNumber == 1) {
640         continue;
641       }
642       for (int i : atomClasses) {
643         if (type.atomClass == i) {
644           return true;
645         }
646       }
647     }
648     return false;
649   }
650 
651   /**
652    * getBondType
653    *
654    * @param key a {@link java.lang.String} object.
655    * @return a {@link ffx.potential.parameters.BondType} object.
656    */
657   public BondType getBondType(String key) {
658     BondType bondType = bondTypes.get(key);
659     if (bondType != null) {
660       bondType.bondUnit = getDouble("BONDUNIT", BondType.DEFAULT_BOND_UNIT);
661       bondType.cubic = getDouble("BOND_CUBIC", BondType.DEFAULT_BOND_CUBIC);
662       bondType.quartic = getDouble("BOND_QUARTIC", BondType.DEFAULT_BOND_QUARTIC);
663       double ridingScale = getDouble("RIDING_HYDROGEN_SCALE", 1.0);
664       if (ridingScale > 1.0 && hasHydrogenAtomClass(bondType.atomClasses)) {
665         bondType.bondUnit *= ridingScale;
666       }
667     }
668     return bondType;
669   }
670 
671   /**
672    * Get BondTypes.
673    *
674    * @return the BondTypes.
675    */
676   public Map<String, BondType> getBondTypes() {
677     // Finalize keywords for Bond Types.
678     for (String key : bondTypes.keySet()) {
679       getBondType(key);
680     }
681     return bondTypes;
682   }
683 
684   /**
685    * getBondType
686    *
687    * @param a1 First AtomType.
688    * @param a2 Second AtomType.
689    * @return a {@link ffx.potential.parameters.BondType} object.
690    */
691   public BondType getBondType(AtomType a1, AtomType a2) {
692     int[] c = {a1.atomClass, a2.atomClass};
693     String key = BondType.sortKey(c);
694     return getBondType(key);
695   }
696 
697   /**
698    * getBonds
699    *
700    * @param moleculeName a {@link java.lang.String} object.
701    * @param atomName     a {@link java.lang.String} object.
702    * @return an array of {@link java.lang.String} objects.
703    */
704   public String[] getBonds(String moleculeName, String atomName) {
705     for (BioType bioType : bioTypes.values()) {
706       if (bioType.moleculeName.equalsIgnoreCase(moleculeName) && bioType.atomName.equalsIgnoreCase(
707           atomName)) {
708         return bioType.bonds;
709       }
710     }
711     return null;
712   }
713 
714   /**
715    * getBoolean
716    *
717    * @param property The property to return.
718    * @return a boolean.
719    * @throws java.lang.Exception if any.
720    */
721   public boolean getBoolean(String property) throws Exception {
722     if (property == null) {
723       throw new Exception("NULL property");
724     }
725     String key = toPropertyForm(property);
726     if (!properties.containsKey(key)) {
727       throw new Exception("Undefined property: " + key);
728     }
729     return properties.getBoolean(key);
730   }
731 
732   /**
733    * getBoolean
734    *
735    * @param property       The property to return.
736    * @param defaultBoolean The default to return.
737    * @return a boolean.
738    */
739   public boolean getBoolean(String property, boolean defaultBoolean) {
740     try {
741       return getBoolean(property);
742     } catch (Exception e) {
743       return defaultBoolean;
744     }
745   }
746 
747   /**
748    * getDouble
749    *
750    * @param property The property to return.
751    * @return The value of the property.
752    * @throws java.lang.Exception if any.
753    */
754   public double getDouble(String property) throws Exception {
755     if (property == null) {
756       throw new Exception("NULL property");
757     }
758     String key = toPropertyForm(property);
759     if (!properties.containsKey(key)) {
760       throw new Exception("Undefined property: " + key);
761     }
762     return properties.getDouble(key);
763   }
764 
765   /**
766    * getDouble
767    *
768    * @param property      The property to return.
769    * @param defaultDouble The default to return.
770    * @return The value of the property.
771    */
772   public double getDouble(String property, Double defaultDouble) {
773     try {
774       return getDouble(property);
775     } catch (Exception e) {
776       return defaultDouble;
777     }
778   }
779 
780   /**
781    * getForceFieldTypeCount
782    *
783    * @param type a {@link ForceField.ForceFieldType} object.
784    * @return The number of ForceFieldTypes of the specified type.
785    */
786   @SuppressWarnings("unchecked")
787   public int getForceFieldTypeCount(ForceFieldType type) {
788     TreeMap<String, BaseType> treeMap = (TreeMap<String, BaseType>) forceFieldTypes.get(type);
789     if (treeMap == null) {
790       logger.log(Level.WARNING, "Unrecognized Force Field Type: {0}", type);
791       return 0;
792     }
793     return treeMap.size();
794   }
795 
796   /**
797    * getImproperType
798    *
799    * @param key a {@link java.lang.String} object.
800    * @return a {@link ffx.potential.parameters.TorsionType} object.
801    */
802   public ImproperTorsionType getImproperType(String key) {
803     ImproperTorsionType improperTorsionType = imptorsTypes.get(key);
804     if (improperTorsionType != null) {
805       double units = getDouble("IMPTORUNIT", ImproperTorsionType.DEFAULT_IMPTOR_UNIT);
806       improperTorsionType.impTorUnit = getDouble("IMPTORSUNIT", units);
807     }
808     return improperTorsionType;
809   }
810 
811   /**
812    * getImproperType
813    *
814    * @return a {@link ffx.potential.parameters.TorsionType} object.
815    */
816   public Collection<ImproperTorsionType> getImproperTypes() {
817     double units = getDouble("IMPTORUNIT", ImproperTorsionType.DEFAULT_IMPTOR_UNIT);
818     units = getDouble("IMPTORSUNIT", units);
819     for (ImproperTorsionType improperTorsionType : imptorsTypes.values()) {
820       improperTorsionType.impTorUnit = units;
821     }
822     return imptorsTypes.values();
823   }
824 
825   /**
826    * getInteger
827    *
828    * @param property The property to return.
829    * @return an int.
830    * @throws java.lang.Exception if any.
831    */
832   public int getInteger(String property) throws Exception {
833     if (property == null) {
834       throw new Exception("NULL property");
835     }
836     String key = toPropertyForm(property);
837     if (!properties.containsKey(key)) {
838       throw new Exception("Undefined property: " + key);
839     }
840     return properties.getInt(key);
841   }
842 
843   /**
844    * getInteger
845    *
846    * @param property       The property to return.
847    * @param defaultInteger The default to return.
848    * @return an int.
849    */
850   public int getInteger(String property, Integer defaultInteger) {
851     try {
852       return getInteger(property);
853     } catch (Exception e) {
854       return defaultInteger;
855     }
856   }
857 
858   /**
859    * getMultipoleType
860    *
861    * @param key a {@link java.lang.String} object.
862    * @return a {@link ffx.potential.parameters.MultipoleType} object.
863    */
864   public MultipoleType getMultipoleType(String key) {
865     return multipoleTypes.get(key);
866   }
867 
868   /**
869    * Find the MultipoleType whose key begins with the supplied String. If there are more than one
870    * MultipoleType that begins with the key, null is returned.
871    *
872    * @param key The key to search for.
873    * @return The MultipoleType if one and only one match is found.
874    */
875   public MultipoleType getMultipoleTypeBeginsWith(String key) {
876     int count = 0;
877     MultipoleType multipoleType = null;
878     for (String s : multipoleTypes.keySet()) {
879       if (s.startsWith(key + " ")) {
880         count++;
881         multipoleType = multipoleTypes.get(s);
882       }
883     }
884 
885     if (count == 1) {
886       return multipoleType;
887     }
888 
889     return null;
890   }
891 
892   /**
893    * Find each MultipoleType whose key begins with the supplied String.
894    *
895    * @param key The key to search for.
896    * @return The MultipoleTypes found.
897    */
898   public List<MultipoleType> getMultipoleTypes(String key) {
899     List<MultipoleType> list = new ArrayList<>();
900     for (String s : multipoleTypes.keySet()) {
901       if (s.startsWith(key + " ")) {
902         list.add(multipoleTypes.get(s));
903       }
904     }
905 
906     return list;
907   }
908 
909   /**
910    * Get MultipoleTypes.
911    *
912    * @return the MultipoleTypes.
913    */
914   public Map<String, MultipoleType> getMultipoleTypes() {
915     return multipoleTypes;
916   }
917 
918   /**
919    * Return all force field types of a given type.
920    *
921    * @param type The type of force field type to clear.
922    * @return a {@link java.util.Map} object.
923    */
924   public Map<String, ? extends BaseType> getTypes(ForceFieldType type) {
925     return forceFieldTypes.get(type);
926   }
927 
928   /**
929    * Clear all force field types of a given type.
930    *
931    * @param type The type of force field type to clear.
932    */
933   public void clearForceFieldType(ForceFieldType type) {
934     Map<String, ? extends BaseType> map = forceFieldTypes.get(type);
935     map.clear();
936   }
937 
938   /**
939    * getOutOfPlaneBendType
940    *
941    * @param key a {@link java.lang.String} object.
942    * @return a {@link ffx.potential.parameters.OutOfPlaneBendType} object.
943    */
944   public OutOfPlaneBendType getOutOfPlaneBendType(String key) {
945     OutOfPlaneBendType outOfPlaneBendType = outOfPlaneBendTypes.get(key);
946     if (outOfPlaneBendType != null) {
947       outOfPlaneBendType.opBendUnit = getDouble("OPBENDUNIT", OutOfPlaneBendType.DEFAULT_OPBEND_UNIT);
948       outOfPlaneBendType.cubic = getDouble("OPBEND-CUBIC", OutOfPlaneBendType.DEFAULT_OPBEND_CUBIC);
949       outOfPlaneBendType.quartic = getDouble("OPBEND-QUARTIC", OutOfPlaneBendType.DEFAULT_OPBEND_QUARTIC);
950       outOfPlaneBendType.pentic = getDouble("OPBEND-PENTIC", OutOfPlaneBendType.DEFAULT_OPBEND_PENTIC);
951       outOfPlaneBendType.sextic = getDouble("OPBEND-SEXTIC", OutOfPlaneBendType.DEFAULT_OPBEND_SEXTIC);
952     }
953     return outOfPlaneBendType;
954   }
955 
956   /**
957    * Get OutOfPlaneBendTypes.
958    *
959    * @return the OutOfPlaneBendTypes.
960    */
961   public Map<String, OutOfPlaneBendType> getOutOfPlaneBendTypes() {
962     // Finalize keywords for OutOfPlaneBend Types.
963     for (String key : outOfPlaneBendTypes.keySet()) {
964       getOutOfPlaneBendType(key);
965     }
966     return outOfPlaneBendTypes;
967   }
968 
969   /**
970    * getOutOfPlaneBendType
971    *
972    * @param a4 AtomType of fourth atom.
973    * @param a0 AtomType of atom 0 of the angle.
974    * @param a1 AtomType of atom 1 of the angle (the trigonal atom).
975    * @param a2 AtomType of atom 2 of the angle.
976    * @return a {@link ffx.potential.parameters.OutOfPlaneBendType} object.
977    */
978   public OutOfPlaneBendType getOutOfPlaneBendType(AtomType a4, AtomType a0, AtomType a1,
979                                                   AtomType a2) {
980     int class4 = a4.atomClass;
981     int class0 = a0.atomClass;
982     int class1 = a1.atomClass;
983     int class2 = a2.atomClass;
984 
985     // First check for an atom4-center-edge-edge type (also checking reversed edges).
986     String key = OutOfPlaneBendType.sortKey(new int[]{class4, class1, class0, class2});
987     OutOfPlaneBendType outOfPlaneBendType = getOutOfPlaneBendType(key);
988     if (outOfPlaneBendType == null) {
989       key = OutOfPlaneBendType.sortKey(new int[]{class4, class1, class2, class0});
990       outOfPlaneBendType = getOutOfPlaneBendType(key);
991     }
992 
993     // Then, check for a generic OOP bend type atom4-center-any-any
994     if (outOfPlaneBendType == null) {
995       key = OutOfPlaneBendType.sortKey(new int[]{class4, class1, 0, 0});
996       outOfPlaneBendType = getOutOfPlaneBendType(key);
997     }
998 
999     return outOfPlaneBendType;
1000   }
1001 
1002   /**
1003    * getPiOrbitalTorsionType
1004    *
1005    * @param key a {@link java.lang.String} object.
1006    * @return a {@link PiOrbitalTorsionType} object.
1007    */
1008   public PiOrbitalTorsionType getPiOrbitalTorsionType(String key) {
1009     PiOrbitalTorsionType piOrbitalTorsionType = piOrbitalTorsionTypes.get(key);
1010     if (piOrbitalTorsionType != null) {
1011       piOrbitalTorsionType.piTorsUnit = getDouble("PITORSUNIT",
1012           PiOrbitalTorsionType.DEFAULT_PITORS_UNIT);
1013     }
1014     return piOrbitalTorsionType;
1015   }
1016 
1017   /**
1018    * Get PiOrbitalTorsions.
1019    *
1020    * @return the PiOrbitalTorsions.
1021    */
1022   public Map<String, PiOrbitalTorsionType> getPiOrbitalTorsionTypes() {
1023     // Finalize keywords for PiOrbitalTorsion Types.
1024     for (String key : piOrbitalTorsionTypes.keySet()) {
1025       getPiOrbitalTorsionType(key);
1026     }
1027     return piOrbitalTorsionTypes;
1028   }
1029 
1030   /**
1031    * getPiOrbitalTorsionType
1032    *
1033    * @param a1 AtomType of atom 1.
1034    * @param a2 AtomType of atom 2.
1035    * @return {@link PiOrbitalTorsionType} object.
1036    */
1037   public PiOrbitalTorsionType getPiOrbitalTorsionType(AtomType a1, AtomType a2) {
1038     int[] c = new int[2];
1039     c[0] = a1.atomClass;
1040     c[1] = a2.atomClass;
1041     String key = PiOrbitalTorsionType.sortKey(c);
1042     return getPiOrbitalTorsionType(key);
1043   }
1044 
1045   /**
1046    * getPolarizeType
1047    *
1048    * @param key a {@link java.lang.String} object.
1049    * @return a {@link ffx.potential.parameters.PolarizeType} object.
1050    */
1051   public PolarizeType getPolarizeType(String key) {
1052     return polarizeTypes.get(key);
1053   }
1054 
1055   /**
1056    * Get PolarizeTypes.
1057    *
1058    * @return the PolarizeTypes.
1059    */
1060   public Map<String, PolarizeType> getPolarizeTypes() {
1061     return polarizeTypes;
1062   }
1063 
1064   /**
1065    * Getter for the field <code>properties</code>.
1066    *
1067    * @return a {@link org.apache.commons.configuration2.CompositeConfiguration} object.
1068    */
1069   public CompositeConfiguration getProperties() {
1070     return properties;
1071   }
1072 
1073   /**
1074    * Getter for the field <code>relativeSolvationTypes</code>.
1075    *
1076    * @return a {@link java.util.HashMap} object.
1077    */
1078   public HashMap<String, RelativeSolvationType> getRelativeSolvationTypes() {
1079     HashMap<String, RelativeSolvationType> types = new HashMap<>();
1080     for (String key : relativeSolvationTypes.keySet()) {
1081       types.put(key, relativeSolvationTypes.get(key));
1082     }
1083     return types;
1084   }
1085 
1086   /**
1087    * Get a SoluteType.
1088    *
1089    * @param key The class of the atom.
1090    * @return The SoluteType if its present.
1091    */
1092   public SoluteType getSoluteType(String key) {
1093     return soluteTypes.get(key);
1094   }
1095 
1096   public Map<String, SoluteType> getSoluteTypes() {
1097     return soluteTypes;
1098   }
1099 
1100   /**
1101    * getStretchBendType
1102    *
1103    * @param key a {@link java.lang.String} object.
1104    * @return a {@link ffx.potential.parameters.StretchBendType} object.
1105    */
1106   public StretchBendType getStretchBendType(String key) {
1107     StretchBendType stretchBendType = stretchBendTypes.get(key);
1108     if (stretchBendType != null) {
1109       stretchBendType.strbndunit = getDouble("STRBNDUNIT", StretchBendType.DEFAULT_STRBND_UNIT);
1110     }
1111     return stretchBendType;
1112   }
1113 
1114   /**
1115    * Get StretchBendTypes.
1116    *
1117    * @return the StretchBendTypes.
1118    */
1119   public Map<String, StretchBendType> getStretchBendTypes() {
1120     // Finalize keywords for StretchBend Types.
1121     for (String key : stretchBendTypes.keySet()) {
1122       getStretchBendType(key);
1123     }
1124     return stretchBendTypes;
1125   }
1126 
1127   /**
1128    * getStretchBendType
1129    *
1130    * @param a1 First AtomType.
1131    * @param a2 Second AtomType.
1132    * @param a3 Third AtomType.
1133    * @return a {@link ffx.potential.parameters.StretchBendType} object.
1134    */
1135   public StretchBendType getStretchBendType(AtomType a1, AtomType a2, AtomType a3) {
1136     int[] c = {a1.atomClass, a2.atomClass, a3.atomClass};
1137     String key = AngleType.sortKey(c);
1138     return getStretchBendType(key);
1139   }
1140 
1141   /**
1142    * getStretchTorsionType
1143    *
1144    * @param key a {@link java.lang.String} object.
1145    * @return a {@link ffx.potential.parameters.StretchTorsionType} object.
1146    */
1147   public StretchTorsionType getStretchTorsionType(String key) {
1148     StretchTorsionType stretchTorsionType = stretchTorsionTypes.get(key);
1149     if (stretchTorsionType != null) {
1150       stretchTorsionType.strTorUnit = getDouble("STRTORUNIT", StretchTorsionType.DEFAULT_STRTOR_UNIT);
1151     }
1152     return stretchTorsionType;
1153   }
1154 
1155   /**
1156    * Get StretchTorsionTypes.
1157    *
1158    * @return the StretchTorsionTypes.
1159    */
1160   public Map<String, StretchTorsionType> getStretchTorsionTypes() {
1161     // Finalize keywords for StretchTorsion Types.
1162     for (String key : stretchTorsionTypes.keySet()) {
1163       getStretchTorsionType(key);
1164     }
1165     return stretchTorsionTypes;
1166   }
1167 
1168   /**
1169    * getBoolean
1170    *
1171    * @param property The property to return.
1172    * @return a String.
1173    * @throws java.lang.Exception if any.
1174    */
1175   public String getString(String property) throws Exception {
1176     if (property == null) {
1177       throw new Exception("NULL property");
1178     }
1179     String key = toPropertyForm(property);
1180     if (!properties.containsKey(key)) {
1181       throw new Exception("Undefined property: " + key);
1182     }
1183     return properties.getString(key);
1184   }
1185 
1186   /**
1187    * getBoolean
1188    *
1189    * @param property      The property to return.
1190    * @param defaultString The default to return.
1191    * @return a boolean.
1192    */
1193   public String getString(String property, String defaultString) {
1194     try {
1195       return getString(property);
1196     } catch (Exception e) {
1197       return defaultString;
1198     }
1199   }
1200 
1201   /**
1202    * getTorsionTorsionType
1203    *
1204    * @param key a {@link java.lang.String} object.
1205    * @return a {@link ffx.potential.parameters.TorsionTorsionType} object.
1206    */
1207   public TorsionTorsionType getTorsionTorsionType(String key) {
1208     TorsionTorsionType torsionTorsionType = torsionTorsionTypes.get(key);
1209     if (torsionTorsionType != null) {
1210       torsionTorsionType.torTorUnit = getDouble("TORTORUNIT",
1211           TorsionTorsionType.DEFAULT_TORTOR_UNIT);
1212     }
1213     return torsionTorsionType;
1214   }
1215 
1216   /**
1217    * Get TorsionTorsionTypes.
1218    *
1219    * @return the TorsionTorsionTypes.
1220    */
1221   public Map<String, TorsionTorsionType> getTorsionTorsionTypes() {
1222     // Finalize keywords for TorsionTorsion Types.
1223     for (String key : torsionTorsionTypes.keySet()) {
1224       getTorsionTorsionType(key);
1225     }
1226     return torsionTorsionTypes;
1227   }
1228 
1229   /**
1230    * getTorsionType
1231    *
1232    * @param key a {@link java.lang.String} object.
1233    * @return a {@link ffx.potential.parameters.TorsionType} object.
1234    */
1235   public TorsionType getTorsionType(String key) {
1236     TorsionType torsionType = torsionTypes.get(key);
1237     if (torsionType != null) {
1238       torsionType.torsionUnit = getDouble("TORSIONUNIT", TorsionType.DEFAULT_TORSION_UNIT);
1239     }
1240     return torsionType;
1241   }
1242 
1243   /**
1244    * Get TorsionTypes.
1245    *
1246    * @return the TorsionTypes.
1247    */
1248   public Map<String, TorsionType> getTorsionTypes() {
1249     // Finalize keywords for Torsion Types.
1250     for (String key : torsionTypes.keySet()) {
1251       getTorsionType(key);
1252     }
1253     return torsionTypes;
1254   }
1255 
1256   /**
1257    * Find a torsion based on the specified classes.
1258    *
1259    * @param c0 Atom class 0.
1260    * @param c1 Atom class 1.
1261    * @param c2 Atom class 2.
1262    * @param c3 Atom class 3.
1263    * @return A torsion type if it exists.
1264    */
1265   private TorsionType getTorsionType(int c0, int c1, int c2, int c3) {
1266     int[] c = {c0, c1, c2, c3};
1267     String key = TorsionType.sortKey(c);
1268     return getTorsionType(key);
1269   }
1270 
1271   /**
1272    * getTorsionType
1273    *
1274    * @param a0 First AtomType.
1275    * @param a1 Second AtomType.
1276    * @param a2 Third AtomType.
1277    * @param a3 Fourth AtomType.
1278    * @return a {@link ffx.potential.parameters.TorsionType} object.
1279    */
1280   public TorsionType getTorsionType(AtomType a0, AtomType a1, AtomType a2, AtomType a3) {
1281     int c0 = a0.atomClass;
1282     int c1 = a1.atomClass;
1283     int c2 = a2.atomClass;
1284     int c3 = a3.atomClass;
1285 
1286     TorsionType torsionType = getTorsionType(c0, c1, c2, c3);
1287 
1288     // Single wild card.
1289     if (torsionType == null) {
1290       if (c0 > c3) {
1291         torsionType = getTorsionType(c0, c1, c2, 0);
1292         if (torsionType == null) {
1293           torsionType = getTorsionType(0, c1, c2, c3);
1294         }
1295       } else {
1296         torsionType = getTorsionType(0, c1, c2, c3);
1297         if (torsionType == null) {
1298           torsionType = getTorsionType(c0, c1, c2, 0);
1299         }
1300       }
1301     }
1302 
1303     // Double wild card.
1304     if (torsionType == null) {
1305       torsionType = getTorsionType(0, c1, c2, 0);
1306     }
1307 
1308     return torsionType;
1309   }
1310 
1311   /**
1312    * getUreyBradleyType
1313    *
1314    * @param key a {@link java.lang.String} object.
1315    * @return a {@link ffx.potential.parameters.UreyBradleyType} object.
1316    */
1317   public UreyBradleyType getUreyBradleyType(String key) {
1318     UreyBradleyType ureyBradleyType = ureyBradleyTypes.get(key);
1319     if (ureyBradleyType != null) {
1320       ureyBradleyType.ureyUnit = getDouble("UREYUNIT", UreyBradleyType.DEFAULT_UREY_UNIT);
1321       ureyBradleyType.cubic = getDouble("UREY_CUBIC", UreyBradleyType.DEFAULT_UREY_CUBIC);
1322       ureyBradleyType.quartic = getDouble("UREY_QUARTIC", UreyBradleyType.DEFAULT_UREY_QUARTIC);
1323       double ridingScale = getDouble("RIDING_HYDROGEN_SCALE", 1.0);
1324       if (ridingScale > 1.0 && hasHydrogenAtomClass(ureyBradleyType.atomClasses)) {
1325         ureyBradleyType.ureyUnit *= ridingScale;
1326       }
1327     }
1328     return ureyBradleyType;
1329   }
1330 
1331   /**
1332    * Get UreyBradleyTypes.
1333    *
1334    * @return the UreyBradleyTypes.
1335    */
1336   public Map<String, UreyBradleyType> getUreyBradleyTypes() {
1337     // Finalize keywords for UreyBradley Types.
1338     for (String key : ureyBradleyTypes.keySet()) {
1339       getUreyBradleyType(key);
1340     }
1341     return ureyBradleyTypes;
1342   }
1343 
1344   /**
1345    * getVDW14Type
1346    *
1347    * @param key a {@link java.lang.String} object.
1348    * @return a {@link ffx.potential.parameters.VDWType} object.
1349    */
1350   public VDWType getVDW14Type(String key) {
1351     return vanderWaals14Types.get(key);
1352   }
1353 
1354   /**
1355    * getVDW14Types
1356    *
1357    * @return a {@link java.util.Map} object.
1358    */
1359   public Map<String, VDWType> getVDW14Types() {
1360     return vanderWaals14Types;
1361   }
1362 
1363   /**
1364    * getVDWType
1365    *
1366    * @param key a {@link java.lang.String} object.
1367    * @return a {@link ffx.potential.parameters.VDWType} object.
1368    */
1369   public VDWType getVDWType(String key) {
1370     return vanderWaalsTypes.get(key);
1371   }
1372 
1373   /**
1374    * getVDWPairType
1375    *
1376    * @param key a {@link java.lang.String} object.
1377    * @return a {@link ffx.potential.parameters.VDWPairType} object.
1378    */
1379   public VDWPairType getVDWPairType(String key) {
1380     return vanderWaalsPairTypes.get(key);
1381   }
1382 
1383   /**
1384    * getVDWTypes
1385    *
1386    * @return a {@link java.util.Map} object.
1387    */
1388   public Map<String, VDWType> getVDWTypes() {
1389     return vanderWaalsTypes;
1390   }
1391 
1392   /**
1393    * getVDWPairTypes
1394    *
1395    * @return a {@link java.util.Map} object.
1396    */
1397   public Map<String, VDWPairType> getVDWPairTypes() {
1398     return vanderWaalsPairTypes;
1399   }
1400 
1401   /**
1402    * Checks if a property was specified.
1403    *
1404    * @param property String to check.
1405    * @return Returns true if the property has been specified.
1406    */
1407   public boolean hasProperty(String property) {
1408     if (property == null) {
1409       return false;
1410     }
1411     String key = toPropertyForm(property);
1412     return properties.containsKey(key);
1413   }
1414 
1415   /**
1416    * log
1417    */
1418   public void log() {
1419     for (ForceFieldType s : forceFieldTypes.keySet()) {
1420       log(s.toString());
1421     }
1422   }
1423 
1424   /**
1425    * Prints any force field keyword to Standard.out.
1426    *
1427    * @param key String
1428    */
1429   public void log(String key) {
1430     ForceFieldType type = ForceFieldType.valueOf(key);
1431     logger.info(toString(type));
1432   }
1433 
1434   /**
1435    * print
1436    */
1437   public void print() {
1438     for (ForceFieldType s : forceFieldTypes.keySet()) {
1439       print(s.toString());
1440     }
1441   }
1442 
1443   /**
1444    * print
1445    *
1446    * @param key a {@link java.lang.String} object.
1447    */
1448   public void print(String key) {
1449     ForceFieldType type = ForceFieldType.valueOf(key);
1450     logger.info(toString(type));
1451   }
1452 
1453   /**
1454    * Renumber ForceField class, type and biotype values.
1455    *
1456    * @param classOffset   The class offset.
1457    * @param typeOffset    The type offset.
1458    * @param bioTypeOffset The biotype offset.
1459    */
1460   public void renumberForceField(int classOffset, int typeOffset, int bioTypeOffset) {
1461     for (AngleType angleType : angleTypes.values()) {
1462       angleType.incrementClasses(classOffset);
1463     }
1464 
1465     for (AngleType angleType : anglepTypes.values()) {
1466       angleType.incrementClasses(classOffset);
1467     }
1468 
1469     for (AtomType atomType : atomTypes.values()) {
1470       atomType.incrementClassAndType(classOffset, typeOffset);
1471     }
1472 
1473     for (BioType bioType : bioTypes.values()) {
1474       bioType.incrementIndexAndType(bioTypeOffset, typeOffset);
1475     }
1476 
1477     for (BondType bondType : bondTypes.values()) {
1478       bondType.incrementClasses(classOffset);
1479     }
1480 
1481     for (MultipoleType multipoleType : multipoleTypes.values()) {
1482       multipoleType.incrementType(typeOffset);
1483     }
1484 
1485     for (OutOfPlaneBendType outOfPlaneBendType : outOfPlaneBendTypes.values()) {
1486       outOfPlaneBendType.incrementClasses(classOffset);
1487     }
1488 
1489     for (PiOrbitalTorsionType piOrbitalTorsionType : piOrbitalTorsionTypes.values()) {
1490       piOrbitalTorsionType.incrementClasses(classOffset);
1491     }
1492 
1493     for (PolarizeType polarizeType : polarizeTypes.values()) {
1494       polarizeType.incrementType(typeOffset);
1495     }
1496 
1497     for (StretchBendType stretchBendType : stretchBendTypes.values()) {
1498       stretchBendType.incrementClasses(classOffset);
1499     }
1500 
1501     for (StretchTorsionType stretchTorsionType : stretchTorsionTypes.values()) {
1502       stretchTorsionType.incrementClasses(classOffset);
1503     }
1504 
1505     for (AngleTorsionType angleTorsionType : angleTorsionTypes.values()) {
1506       angleTorsionType.incrementClasses(classOffset);
1507     }
1508 
1509     for (TorsionTorsionType torsionTorsionType : torsionTorsionTypes.values()) {
1510       torsionTorsionType.incrementClasses(classOffset);
1511     }
1512 
1513     for (TorsionType torsionType : torsionTypes.values()) {
1514       torsionType.incrementClasses(classOffset);
1515     }
1516 
1517     for (TorsionType torsionType : improperTypes.values()) {
1518       torsionType.incrementClasses(classOffset);
1519     }
1520 
1521     for (ImproperTorsionType improperTorsionType : imptorsTypes.values()) {
1522       improperTorsionType.incrementClasses(classOffset);
1523     }
1524 
1525     for (UreyBradleyType ureyBradleyType : ureyBradleyTypes.values()) {
1526       ureyBradleyType.incrementClasses(classOffset);
1527     }
1528 
1529     for (VDWType vanderWaalsType : vanderWaalsTypes.values()) {
1530       vanderWaalsType.incrementClass(classOffset);
1531     }
1532 
1533     for (VDWType vanderWaals14Type : vanderWaals14Types.values()) {
1534       vanderWaals14Type.incrementClass(classOffset);
1535     }
1536 
1537     for (VDWPairType vanderWaalsPairType : vanderWaalsPairTypes.values()) {
1538       vanderWaalsPairType.incrementClasses(classOffset);
1539     }
1540 
1541     for (SoluteType soluteType : soluteTypes.values()) {
1542       soluteType.incrementType(typeOffset);
1543     }
1544   }
1545 
1546   /**
1547    * The AngleFunction in use by this ForceField.
1548    *
1549    * @param angleFunction The AngleFunction to use.
1550    */
1551   public void setAngleFunction(AngleType.AngleFunction angleFunction) {
1552     for (AngleType angleType : anglepTypes.values()) {
1553       angleType.setAngleFunction(angleFunction);
1554     }
1555     for (AngleType angleType : angleTypes.values()) {
1556       angleType.setAngleFunction(angleFunction);
1557     }
1558   }
1559 
1560   /**
1561    * The BondFunction in use by this ForceField.
1562    *
1563    * @param bondFunction The BondFunction to use.
1564    */
1565   public void setBondFunction(BondType.BondFunction bondFunction) {
1566     for (BondType bondType : bondTypes.values()) {
1567       bondType.setBondFunction(bondFunction);
1568     }
1569   }
1570 
1571   /**
1572    * Return a String for any Force Field keyword.
1573    *
1574    * @param type ForceFieldType
1575    * @return String
1576    */
1577   public String toString(ForceFieldType type) {
1578     StringBuilder sb = new StringBuilder("\n");
1579     Map<String, ? extends BaseType> t = forceFieldTypes.get(type);
1580 
1581     if (t.isEmpty()) {
1582       return "";
1583     }
1584 
1585     for (Object o : t.values()) {
1586       sb.append(o.toString()).append("\n");
1587     }
1588     return sb.toString();
1589   }
1590 
1591   /**
1592    * {@inheritDoc}
1593    */
1594   @Override
1595   public String toString() {
1596     String forceFieldName;
1597     try {
1598       forceFieldName = getString("FORCEFIELD");
1599     } catch (Exception ex) {
1600       forceFieldName = "Unknown";
1601     }
1602     return forceFieldName;
1603   }
1604 
1605   /**
1606    * toString
1607    *
1608    * @param key a {@link java.lang.String} object.
1609    * @return a {@link java.lang.String} object.
1610    */
1611   public String toString(String key) {
1612     if (key == null) {
1613       return null;
1614     }
1615 
1616     key = toPropertyForm(key);
1617 
1618     if (properties.containsKey(key)) {
1619       List<Object> l = properties.getList(key);
1620       return key + " " + Arrays.toString(l.toArray());
1621     } else {
1622       return key + " is not defined.";
1623     }
1624   }
1625 
1626   /**
1627    * toStringBuffer
1628    *
1629    * @return Returns a StringBuffer representation of the ForceField.
1630    */
1631   public StringBuffer toStringBuffer() {
1632     StringBuffer sb = new StringBuffer();
1633     for (ForceFieldType s : forceFieldTypes.keySet()) {
1634       ForceFieldType type = ForceFieldType.valueOf(s.toString());
1635       sb.append(toString(type));
1636     }
1637     return sb;
1638   }
1639 
1640   /**
1641    * Uses OpenMMXmlFilter to create an XML file of the force field usable by OpenMM.
1642    */
1643   public void toXML() throws Exception {
1644     OpenMMXmlFilter xmlFilter = new OpenMMXmlFilter(this);
1645     xmlFilter.toXML();
1646   }
1647 
1648   /**
1649    * All atoms whose atomName begins with <code>name</code> for the given molecule will be updated to
1650    * the new type. For an atomName such as CD, it will map to both CD1 and CD2.
1651    *
1652    * @param molecule The molecule name.
1653    * @param atom     The atom name.
1654    * @param newType  The new atom type.
1655    * @return The AtomType that was replaced.
1656    */
1657   private AtomType updateBioType(String molecule, String atom, int newType) {
1658     int oldType = 0;
1659     for (BioType bioType : bioTypes.values()) {
1660       if (bioType.moleculeName.equalsIgnoreCase(molecule)) {
1661         if (atom.length() <= bioType.atomName.length()) {
1662           if (bioType.atomName.toUpperCase().startsWith(atom.toUpperCase())) {
1663             oldType = bioType.atomType;
1664             bioType.atomType = newType;
1665           }
1666         }
1667       }
1668     }
1669     return getAtomType(Integer.toString(oldType));
1670   }
1671 
1672   /**
1673    * Patches that add new atom classes/types that bond to existing atom classes/types require
1674    * "hybrid" force field types that include a mixture of new and existing types.
1675    *
1676    * @param typeMap    A look-up from new types to existing types.
1677    * @param patchTypes a {@link java.util.HashMap} object.
1678    */
1679   private void patchClassesAndTypes(HashMap<AtomType, AtomType> typeMap,
1680                                     HashMap<String, AtomType> patchTypes) {
1681 
1682     for (BondType bondType : bondTypes.values().toArray(new BondType[0])) {
1683       BondType newType = bondType.patchClasses(typeMap);
1684       if (newType != null) {
1685         logger.info(" " + newType);
1686         addForceFieldType(newType);
1687       }
1688     }
1689 
1690     for (AngleType angleType : angleTypes.values().toArray(new AngleType[0])) {
1691       AngleType newType = angleType.patchClasses(typeMap);
1692       if (newType != null) {
1693         logger.info(" " + newType);
1694         addForceFieldType(newType);
1695       }
1696     }
1697 
1698     for (OutOfPlaneBendType outOfPlaneBendType : outOfPlaneBendTypes.values()
1699         .toArray(new OutOfPlaneBendType[0])) {
1700       OutOfPlaneBendType newType = outOfPlaneBendType.patchClasses(typeMap);
1701       if (newType != null) {
1702         logger.info(" " + newType);
1703         addForceFieldType(newType);
1704       }
1705     }
1706 
1707     for (PiOrbitalTorsionType piOrbitalTorsionType : piOrbitalTorsionTypes.values()
1708         .toArray(new PiOrbitalTorsionType[0])) {
1709       PiOrbitalTorsionType newType = piOrbitalTorsionType.patchClasses(typeMap);
1710       if (newType != null) {
1711         logger.info(" " + newType);
1712         addForceFieldType(newType);
1713       }
1714     }
1715 
1716     for (StretchBendType stretchBendType : stretchBendTypes.values()
1717         .toArray(new StretchBendType[0])) {
1718       StretchBendType newType = stretchBendType.patchClasses(typeMap);
1719       if (newType != null) {
1720         logger.info(" " + newType);
1721         addForceFieldType(newType);
1722       }
1723     }
1724 
1725     /* for (TorsionTorsionType torsionTorsionType :
1726      * torsionTorsionTypes.values().toArray(new TorsionTorsionType[0])) {
1727      * String currentKey = torsionTorsionType.key;
1728      * torsionTorsionType.patchClasses(typeMap); if
1729      * (!torsionTorsionType.key.equals(currentKey)) {
1730      * torsionTorsionTypes.remove(currentKey);
1731      * addForceFieldType(torsionTorsionType); } }
1732      */
1733 
1734     for (TorsionType torsionType : torsionTypes.values().toArray(new TorsionType[0])) {
1735       TorsionType newType = torsionType.patchClasses(typeMap);
1736       if (newType != null) {
1737         logger.info(" " + newType);
1738         addForceFieldType(newType);
1739       }
1740     }
1741 
1742     /*
1743     for (ImproperTorsionType improperType : imptorsTypes.values().toArray(new ImproperTorsionType[0])) {
1744         String currentKey = improperType.key;
1745         improperType.patchClasses(typeMap);
1746         if (!improperType.key.equals(currentKey)) {
1747             torsionTypes.remove(currentKey);
1748             addForceFieldType(improperType);
1749         }
1750     }
1751 
1752     for (UreyBradleyType ureyBradleyType : ureyBradleyTypes.values().toArray(new UreyBradleyType[0])) {
1753         String currentKey = ureyBradleyType.key;
1754         ureyBradleyType.patchClasses(typeMap);
1755         if (!ureyBradleyType.key.equals(currentKey)) {
1756             ureyBradleyTypes.remove(currentKey);
1757             addForceFieldType(ureyBradleyType);
1758         }
1759     } */
1760 
1761     for (MultipoleType multipoleType : multipoleTypes.values().toArray(new MultipoleType[0])) {
1762       MultipoleType newType = multipoleType.patchTypes(typeMap);
1763       if (newType != null) {
1764         logger.info(" " + newType);
1765         addForceFieldType(newType);
1766       }
1767     }
1768 
1769     try {
1770       for (AtomType atomType : patchTypes.values()) {
1771         PolarizeType polarizeType = getPolarizeType(atomType.key);
1772         if (polarizeType != null && polarizeType.patchTypes(typeMap)) {
1773           logger.info(" " + polarizeType);
1774         }
1775       }
1776     } catch (Exception e) {
1777       // Inefficient hack. Should actually check if polarizeTypes are necessary.
1778     }
1779   }
1780 
1781   /**
1782    * Returns the minimum atom class value.
1783    *
1784    * @return The minimum atom class value.
1785    */
1786   private int minClass() {
1787     int minClass = maxClass();
1788     for (AtomType type : atomTypes.values()) {
1789       if (type.atomClass < minClass) {
1790         minClass = type.atomClass;
1791       }
1792     }
1793     return minClass;
1794   }
1795 
1796   /**
1797    * Returns the minimum atom type value.
1798    *
1799    * @return The minimum atom type value.
1800    */
1801   private int minType() {
1802     int minType = maxType();
1803     for (String key : atomTypes.keySet()) {
1804       int type = Integer.parseInt(key);
1805       if (type < minType) {
1806         minType = type;
1807       }
1808     }
1809     return minType;
1810   }
1811 
1812   /**
1813    * Returns the minimum Biotype value.
1814    *
1815    * @return The minimum Biotype value.
1816    */
1817   private int minBioType() {
1818     int minBioType = maxBioType();
1819     for (String key : bioTypes.keySet()) {
1820       int type = Integer.parseInt(key);
1821       if (type < minBioType) {
1822         minBioType = type;
1823       }
1824     }
1825     return minBioType;
1826   }
1827 
1828   /**
1829    * Returns the maximum atom class value.
1830    *
1831    * @return The maximum atom class value.
1832    */
1833   private int maxClass() {
1834     int maxClass = 0;
1835     for (AtomType type : atomTypes.values()) {
1836       if (type.atomClass > maxClass) {
1837         maxClass = type.atomClass;
1838       }
1839     }
1840     return maxClass;
1841   }
1842 
1843   /**
1844    * Returns the maximum atom type value.
1845    *
1846    * @return The maximum atom type value.
1847    */
1848   private int maxType() {
1849     int maxType = 0;
1850     for (String key : atomTypes.keySet()) {
1851       int type = Integer.parseInt(key);
1852       if (type > maxType) {
1853         maxType = type;
1854       }
1855     }
1856     return maxType;
1857   }
1858 
1859   /**
1860    * Returns the maximum Biotype.
1861    *
1862    * @return The maximum Biotype.
1863    */
1864   private int maxBioType() {
1865     int maxBioType = 0;
1866     for (String key : bioTypes.keySet()) {
1867       int type = Integer.parseInt(key);
1868       if (type > maxBioType) {
1869         maxBioType = type;
1870       }
1871     }
1872     return maxBioType;
1873   }
1874 
1875   private void modifiedResidue(String modres) {
1876     String[] tokens = modres.trim().split(" +");
1877     String modResname = tokens[0].toUpperCase();
1878     String stdName = tokens[1].toUpperCase();
1879     HashMap<String, AtomType> patchAtomTypes = getAtomTypes(modResname);
1880     HashMap<String, AtomType> stdAtomTypes = getAtomTypes(stdName);
1881 
1882     HashMap<String, AtomType> patchTypes = new HashMap<>();
1883     int len = tokens.length;
1884     for (int i = 2; i < len; i++) {
1885       String atomName = tokens[i].toUpperCase();
1886       if (!patchTypes.containsKey(atomName) && patchAtomTypes.containsKey(atomName)) {
1887         AtomType type = patchAtomTypes.get(atomName);
1888         patchTypes.put(atomName, type);
1889       }
1890     }
1891 
1892     HashMap<AtomType, AtomType> typeMap = new HashMap<>();
1893     for (String atomName : stdAtomTypes.keySet()) {
1894       boolean found = false;
1895       for (int i = 2; i < len; i++) {
1896         if (atomName.equalsIgnoreCase(tokens[i])) {
1897           found = true;
1898           break;
1899         }
1900       }
1901       if (!found) {
1902         AtomType stdType = stdAtomTypes.get(atomName);
1903         // Edit new BioType records to point to an existing force field type.
1904         AtomType patchType = updateBioType(modResname, atomName, stdType.type);
1905         if (patchType != null) {
1906           typeMap.put(patchType, stdType);
1907           logger.info(" " + patchType + " -> " + stdType);
1908         }
1909       }
1910     }
1911 
1912     patchClassesAndTypes(typeMap, patchTypes);
1913   }
1914 
1915   /**
1916    * If some set of other boolean values imply another boolean is true, set that implied boolean to
1917    * true.
1918    *
1919    * <p>First designed for LAMBDATERM, which is implied by any of VDW_LAMBDATERM, ELEC_LAMBDATERM,
1920    * or GK_LAMBDATERM.
1921    *
1922    * @param toSet         Property to set true if otherBooleans true.
1923    * @param otherBooleans Properties that imply toSet is true.
1924    */
1925   private void trueImpliedBoolean(String toSet, String... otherBooleans) {
1926     // Short-circuit if it's already true.
1927     if (getBoolean(toSet, false)) {
1928       return;
1929     }
1930     // Check all the other booleans that imply toSet.
1931     for (String otherBool : otherBooleans) {
1932       if (getBoolean(otherBool, false)) {
1933         addProperty(toSet, "true");
1934         logger.info(format(" Setting implied boolean %s true due to boolean %s", toSet, otherBool));
1935       }
1936     }
1937   }
1938 
1939   /**
1940    * Check for self-consistent polarization groups.
1941    */
1942   private void checkPolarizationTypes() {
1943     boolean change = false;
1944     for (String key : polarizeTypes.keySet()) {
1945       PolarizeType polarizeType = polarizeTypes.get(key);
1946       int orig = Integer.parseInt(key);
1947       int[] types = polarizeType.polarizationGroup;
1948       if (types == null) {
1949         continue;
1950       }
1951 
1952       for (int type : types) {
1953         String key2 = Integer.toString(type);
1954         PolarizeType polarizeType2 = polarizeTypes.get(key2);
1955         if (polarizeType2 == null) {
1956           logger.severe(
1957               format("Polarize type %s references nonexistant polarize type %s.", key, key2));
1958           continue;
1959         }
1960         int[] types2 = polarizeType2.polarizationGroup;
1961         if (types2 == null) {
1962           polarizeType2.add(orig);
1963           change = true;
1964           continue;
1965         }
1966         boolean found = false;
1967         for (int type2 : types2) {
1968           for (int type3 : types) {
1969             if (type2 == type3) {
1970               found = true;
1971               break;
1972             }
1973           }
1974           if (!found) {
1975             polarizeType.add(type2);
1976             change = true;
1977           }
1978         }
1979       }
1980     }
1981     if (change) {
1982       checkPolarizationTypes();
1983     }
1984   }
1985 
1986   public enum ELEC_FORM {
1987     PAM, FIXED_CHARGE
1988   }
1989 
1990   /**
1991    * Available force fields.
1992    */
1993   public enum ForceFieldName {
1994     // Amber FF 1994.
1995     AMBER_1994,
1996     // Amber FF 1996.
1997     AMBER_1996,
1998     // Amber FF 1998.
1999     AMBER_1998,
2000     // Amber FF 1999.
2001     AMBER_1999,
2002     // Amber FF 1999 SB.
2003     AMBER_1999_SB,
2004     // AMOEBA Small Molecule Force Field 2004.
2005     AMOEBA_2004,
2006     // AMOEBA Small Molecule Force Field 2009.
2007     AMOEBA_2009,
2008     // AMOEBA Biomolecular Force Field 2009.
2009     AMOEBA_BIO_2009,
2010     // AMOEBA Biomolecular Force Field 2018.
2011     AMOEBA_BIO_2018,
2012     // AMOEBA Biomolecular Force Field 2018 for CpHMD.
2013     AMOEBA_BIO_2018_CPHMD,
2014     // AMOEBA Nucleic Acid Force Field 2017.
2015     AMOEBA_NUC_2017,
2016     // AMOEBA Protein Force Field 2004.
2017     AMOEBA_PROTEIN_2004,
2018     // AMOEBA Protein Force Field 2013.
2019     AMOEBA_PROTEIN_2013,
2020     // AMOEBA Water Force Field 2003.
2021     AMOEBA_WATER_2003,
2022     // AMOEBA Water Force Field 2014.
2023     AMOEBA_WATER_2014,
2024     // CHARMM 22 Force Field.
2025     CHARMM_22,
2026     // CHARMM 27 Force Field.
2027     CHARMM_27,
2028     // CHARMM 36 Force Field.
2029     CHARMM_36,
2030     // Direct AMOEBA Water Force Field.
2031     IAMOEBA_WATER,
2032     // OPLS-AA Force Field.
2033     OPLS_AA_2008,
2034     // OPLS-AA/L Force Field.
2035     OPLS_AAL
2036   }
2037 
2038   public enum ForceFieldType {
2039     KEYWORD, ANGLE, ANGLEP, ANGTORS, ATOM, BIOTYPE, BOND, CHARGE, IMPROPER, IMPTORS, MULTIPOLE, OPBEND, PITORS, POLARIZE, SOLUTE, STRBND, STRTORS, TORSION, TORTORS, UREYBRAD, VDW, VDW14, VDWPR, VDWPAIR, RELATIVESOLV
2040   }
2041 
2042 }