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-2024.
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 static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
41  import static java.lang.String.format;
42  
43  import ffx.utilities.FFXProperty;
44  
45  import java.net.URL;
46  import java.util.ArrayList;
47  import java.util.Arrays;
48  import java.util.Collection;
49  import java.util.EnumMap;
50  import java.util.HashMap;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.TreeMap;
54  import java.util.logging.Level;
55  import java.util.logging.Logger;
56  
57  import org.apache.commons.configuration2.CompositeConfiguration;
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       angleTypes.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    * getAngleType
453    *
454    * @param key a {@link java.lang.String} object.
455    * @return a {@link ffx.potential.parameters.AngleType} object.
456    */
457   public AngleType getAngleType(String key) {
458     AngleType angleType = angleTypes.get(key);
459     if (angleType == null) {
460       angleType = anglepTypes.get(key);
461     }
462     if (angleType != null) {
463       angleType.angleUnit = getDouble("ANGLEUNIT", AngleType.DEFAULT_ANGLE_UNIT);
464       angleType.cubic = getDouble("ANGLE-CUBIC", AngleType.DEFAULT_ANGLE_CUBIC);
465       angleType.quartic = getDouble("ANGLE-QUARTIC", AngleType.DEFAULT_ANGLE_QUARTIC);
466       angleType.pentic = getDouble("ANGLE-PENTIC", AngleType.DEFAULT_ANGLE_PENTIC);
467       angleType.sextic = getDouble("ANGLE-SEXTIC", AngleType.DEFAULT_ANGLE_SEXTIC);
468     }
469     return angleType;
470   }
471 
472   /**
473    * getAngleType
474    *
475    * @param a1 First AtomType.
476    * @param a2 Second AtomType.
477    * @param a3 Third AtomType.
478    * @return a {@link ffx.potential.parameters.AngleType} object.
479    */
480   public AngleType getAngleType(AtomType a1, AtomType a2, AtomType a3) {
481     int[] c = {a1.atomClass, a2.atomClass, a3.atomClass};
482     String key = AngleType.sortKey(c);
483     return getAngleType(key);
484   }
485 
486   /**
487    * getAtomType
488    *
489    * @param key a {@link java.lang.String} object.
490    * @return a {@link ffx.potential.parameters.AtomType} object.
491    */
492   public AtomType getAtomType(String key) {
493     return atomTypes.get(key);
494   }
495 
496   /**
497    * getAtomType
498    *
499    * @param moleculeName a {@link java.lang.String} object.
500    * @param atomName     a {@link java.lang.String} object.
501    * @return a {@link ffx.potential.parameters.AtomType} object.
502    */
503   public AtomType getAtomType(String moleculeName, String atomName) {
504     for (BioType bioType : bioTypes.values()) {
505       if (bioType.moleculeName.equalsIgnoreCase(moleculeName) && bioType.atomName.equalsIgnoreCase(
506           atomName)) {
507         String key = Integer.toString(bioType.atomType);
508         return atomTypes.get(key);
509       }
510     }
511     return null;
512   }
513 
514   /**
515    * Getter for the field <code>atomTypes</code>.
516    *
517    * @param atomType AtomType to find similar examples of.
518    * @return Similar atom types.
519    */
520   public List<AtomType> getSimilarAtomTypes(AtomType atomType) {
521     List<AtomType> types = new ArrayList<>();
522     for (AtomType type : atomTypes.values()) {
523       if (type.atomicNumber == atomType.atomicNumber && type.valence == atomType.valence) {
524         types.add(type);
525       }
526     }
527     return types;
528   }
529 
530   /**
531    * Getter for the field <code>atomTypes</code>.
532    *
533    * @param moleculeName a {@link java.lang.String} object.
534    * @return a {@link java.util.HashMap} object.
535    */
536   public HashMap<String, AtomType> getAtomTypes(String moleculeName) {
537     HashMap<String, AtomType> types = new HashMap<>();
538     for (BioType bioType : bioTypes.values()) {
539       if (bioType.moleculeName.equalsIgnoreCase(moleculeName)) {
540         String key = Integer.toString(bioType.atomType);
541         AtomType type = atomTypes.get(key);
542         types.put(bioType.atomName.toUpperCase(), type);
543       }
544     }
545     return types;
546   }
547 
548   /**
549    * getBioType.
550    *
551    * @param moleculeName a {@link java.lang.String} object.
552    * @param atomName     a {@link java.lang.String} object.
553    * @return a {@link ffx.potential.parameters.BioType} object.
554    */
555   public BioType getBioType(String moleculeName, String atomName) {
556     for (BioType bioType : bioTypes.values()) {
557       if (bioType.moleculeName.equalsIgnoreCase(moleculeName) && bioType.atomName.equalsIgnoreCase(
558           atomName)) {
559         return bioType;
560       }
561     }
562     return null;
563   }
564 
565   /**
566    * getBioType
567    *
568    * @param key a {@link java.lang.String} object.
569    * @return a {@link ffx.potential.parameters.BioType} object.
570    */
571   public BioType getBioType(String key) {
572     return bioTypes.get(key);
573   }
574 
575   /**
576    * getBioTypeMap.
577    *
578    * @return a {@link java.util.Map} object.
579    */
580   public Map<String, BioType> getBioTypeMap() {
581     return bioTypes;
582   }
583 
584   /**
585    * getBondType
586    *
587    * @param key a {@link java.lang.String} object.
588    * @return a {@link ffx.potential.parameters.BondType} object.
589    */
590   public BondType getBondType(String key) {
591     BondType bondType = bondTypes.get(key);
592     if (bondType != null) {
593       bondType.bondUnit = getDouble("BONDUNIT", BondType.DEFAULT_BOND_UNIT);
594       bondType.cubic = getDouble("BOND_CUBIC", BondType.DEFAULT_BOND_CUBIC);
595       bondType.quartic = getDouble("BOND_QUARTIC", BondType.DEFAULT_BOND_QUARTIC);
596     }
597     return bondType;
598   }
599 
600   /**
601    * getBondType
602    *
603    * @param a1 First AtomType.
604    * @param a2 Second AtomType.
605    * @return a {@link ffx.potential.parameters.BondType} object.
606    */
607   public BondType getBondType(AtomType a1, AtomType a2) {
608     int[] c = {a1.atomClass, a2.atomClass};
609     String key = BondType.sortKey(c);
610     return getBondType(key);
611   }
612 
613   /**
614    * getBonds
615    *
616    * @param moleculeName a {@link java.lang.String} object.
617    * @param atomName     a {@link java.lang.String} object.
618    * @return an array of {@link java.lang.String} objects.
619    */
620   public String[] getBonds(String moleculeName, String atomName) {
621     for (BioType bioType : bioTypes.values()) {
622       if (bioType.moleculeName.equalsIgnoreCase(moleculeName) && bioType.atomName.equalsIgnoreCase(
623           atomName)) {
624         return bioType.bonds;
625       }
626     }
627     return null;
628   }
629 
630   /**
631    * getBoolean
632    *
633    * @param property The property to return.
634    * @return a boolean.
635    * @throws java.lang.Exception if any.
636    */
637   public boolean getBoolean(String property) throws Exception {
638     if (property == null) {
639       throw new Exception("NULL property");
640     }
641     String key = toPropertyForm(property);
642     if (!properties.containsKey(key)) {
643       throw new Exception("Undefined property: " + key);
644     }
645     return properties.getBoolean(key);
646   }
647 
648   /**
649    * getBoolean
650    *
651    * @param property       The property to return.
652    * @param defaultBoolean The default to return.
653    * @return a boolean.
654    */
655   public boolean getBoolean(String property, boolean defaultBoolean) {
656     try {
657       return getBoolean(property);
658     } catch (Exception e) {
659       return defaultBoolean;
660     }
661   }
662 
663   /**
664    * getDouble
665    *
666    * @param property The property to return.
667    * @return The value of the property.
668    * @throws java.lang.Exception if any.
669    */
670   public double getDouble(String property) throws Exception {
671     if (property == null) {
672       throw new Exception("NULL property");
673     }
674     String key = toPropertyForm(property);
675     if (!properties.containsKey(key)) {
676       throw new Exception("Undefined property: " + key);
677     }
678     return properties.getDouble(key);
679   }
680 
681   /**
682    * getDouble
683    *
684    * @param property      The property to return.
685    * @param defaultDouble The default to return.
686    * @return The value of the property.
687    */
688   public double getDouble(String property, Double defaultDouble) {
689     try {
690       return getDouble(property);
691     } catch (Exception e) {
692       return defaultDouble;
693     }
694   }
695 
696   /**
697    * getForceFieldTypeCount
698    *
699    * @param type a {@link ForceField.ForceFieldType} object.
700    * @return The number of ForceFieldTypes of the specified type.
701    */
702   @SuppressWarnings("unchecked")
703   public int getForceFieldTypeCount(ForceFieldType type) {
704     TreeMap<String, BaseType> treeMap = (TreeMap<String, BaseType>) forceFieldTypes.get(type);
705     if (treeMap == null) {
706       logger.log(Level.WARNING, "Unrecognized Force Field Type: {0}", type);
707       return 0;
708     }
709     return treeMap.size();
710   }
711 
712   /**
713    * getImproperType
714    *
715    * @param key a {@link java.lang.String} object.
716    * @return a {@link ffx.potential.parameters.TorsionType} object.
717    */
718   public ImproperTorsionType getImproperType(String key) {
719     ImproperTorsionType improperTorsionType = imptorsTypes.get(key);
720     if (improperTorsionType != null) {
721       double units = getDouble("IMPTORUNIT", ImproperTorsionType.DEFAULT_IMPTOR_UNIT);
722       improperTorsionType.impTorUnit = getDouble("IMPTORSUNIT", units);
723     }
724     return improperTorsionType;
725   }
726 
727   /**
728    * getImproperType
729    *
730    * @return a {@link ffx.potential.parameters.TorsionType} object.
731    */
732   public Collection<ImproperTorsionType> getImproperTypes() {
733     double units = getDouble("IMPTORUNIT", ImproperTorsionType.DEFAULT_IMPTOR_UNIT);
734     units = getDouble("IMPTORSUNIT", units);
735     for (ImproperTorsionType improperTorsionType : imptorsTypes.values()) {
736       improperTorsionType.impTorUnit = units;
737     }
738     return imptorsTypes.values();
739   }
740 
741   /**
742    * getInteger
743    *
744    * @param property The property to return.
745    * @return an int.
746    * @throws java.lang.Exception if any.
747    */
748   public int getInteger(String property) throws Exception {
749     if (property == null) {
750       throw new Exception("NULL property");
751     }
752     String key = toPropertyForm(property);
753     if (!properties.containsKey(key)) {
754       throw new Exception("Undefined property: " + key);
755     }
756     return properties.getInt(key);
757   }
758 
759   /**
760    * getInteger
761    *
762    * @param property       The property to return.
763    * @param defaultInteger The default to return.
764    * @return an int.
765    */
766   public int getInteger(String property, Integer defaultInteger) {
767     try {
768       return getInteger(property);
769     } catch (Exception e) {
770       return defaultInteger;
771     }
772   }
773 
774   /**
775    * getMultipoleType
776    *
777    * @param key a {@link java.lang.String} object.
778    * @return a {@link ffx.potential.parameters.MultipoleType} object.
779    */
780   public MultipoleType getMultipoleType(String key) {
781     return multipoleTypes.get(key);
782   }
783 
784   /**
785    * Find the MultipoleType whose key begins with the supplied String. If there are more than one
786    * MultipoleType that begins with the key, null is returned.
787    *
788    * @param key The key to search for.
789    * @return The MultipoleType if one and only one match is found.
790    */
791   public MultipoleType getMultipoleTypeBeginsWith(String key) {
792     int count = 0;
793     MultipoleType multipoleType = null;
794     for (String s : multipoleTypes.keySet()) {
795       if (s.startsWith(key + " ")) {
796         count++;
797         multipoleType = multipoleTypes.get(s);
798       }
799     }
800 
801     if (count == 1) {
802       return multipoleType;
803     }
804 
805     return null;
806   }
807 
808   /**
809    * Find each MultipoleType whose key begins with the supplied String.
810    *
811    * @param key The key to search for.
812    * @return The MultipoleTypes found.
813    */
814   public List<MultipoleType> getMultipoleTypes(String key) {
815     List<MultipoleType> list = new ArrayList<>();
816     for (String s : multipoleTypes.keySet()) {
817       if (s.startsWith(key + " ")) {
818         list.add(multipoleTypes.get(s));
819       }
820     }
821 
822     return list;
823   }
824 
825   /**
826    * Return all force field types of a given type.
827    *
828    * @param type The type of force field type to clear.
829    * @return a {@link java.util.Map} object.
830    */
831   public Map<String, ? extends BaseType> getTypes(ForceFieldType type) {
832     return forceFieldTypes.get(type);
833   }
834 
835   /**
836    * Clear all force field types of a given type.
837    *
838    * @param type The type of force field type to clear.
839    */
840   public void clearForceFieldType(ForceFieldType type) {
841     Map<String, ? extends BaseType> map = forceFieldTypes.get(type);
842     map.clear();
843   }
844 
845   /**
846    * getOutOfPlaneBendType
847    *
848    * @param key a {@link java.lang.String} object.
849    * @return a {@link ffx.potential.parameters.OutOfPlaneBendType} object.
850    */
851   public OutOfPlaneBendType getOutOfPlaneBendType(String key) {
852     OutOfPlaneBendType outOfPlaneBendType = outOfPlaneBendTypes.get(key);
853     if (outOfPlaneBendType != null) {
854       outOfPlaneBendType.opBendUnit = getDouble("OPBENDUNIT",
855           OutOfPlaneBendType.DEFAULT_OPBEND_UNIT);
856       outOfPlaneBendType.cubic = getDouble("OPBEND-CUBIC", OutOfPlaneBendType.DEFAULT_OPBEND_CUBIC);
857       outOfPlaneBendType.quartic = getDouble("OPBEND-QUARTIC",
858           OutOfPlaneBendType.DEFAULT_OPBEND_QUARTIC);
859       outOfPlaneBendType.pentic = getDouble("OPBEND-PENTIC",
860           OutOfPlaneBendType.DEFAULT_OPBEND_PENTIC);
861       outOfPlaneBendType.sextic = getDouble("OPBEND-SEXTIC",
862           OutOfPlaneBendType.DEFAULT_OPBEND_SEXTIC);
863     }
864     return outOfPlaneBendType;
865   }
866 
867   /**
868    * getOutOfPlaneBendType
869    *
870    * @param a4 AtomType of fourth atom.
871    * @param a0 AtomType of atom 0 of the angle.
872    * @param a1 AtomType of atom 1 of the angle (the trigonal atom).
873    * @param a2 AtomType of atom 2 of the angle.
874    * @return a {@link ffx.potential.parameters.OutOfPlaneBendType} object.
875    */
876   public OutOfPlaneBendType getOutOfPlaneBendType(AtomType a4, AtomType a0, AtomType a1,
877                                                   AtomType a2) {
878     int class4 = a4.atomClass;
879     int class0 = a0.atomClass;
880     int class1 = a1.atomClass;
881     int class2 = a2.atomClass;
882 
883     // First check for an atom4-center-edge-edge type (also checking reversed edges).
884     String key = OutOfPlaneBendType.sortKey(new int[]{class4, class1, class0, class2});
885     OutOfPlaneBendType outOfPlaneBendType = getOutOfPlaneBendType(key);
886     if (outOfPlaneBendType == null) {
887       key = OutOfPlaneBendType.sortKey(new int[]{class4, class1, class2, class0});
888       outOfPlaneBendType = getOutOfPlaneBendType(key);
889     }
890 
891     // Then, check for a generic OOP bend type atom4-center-any-any
892     if (outOfPlaneBendType == null) {
893       key = OutOfPlaneBendType.sortKey(new int[]{class4, class1, 0, 0});
894       outOfPlaneBendType = getOutOfPlaneBendType(key);
895     }
896 
897     return outOfPlaneBendType;
898   }
899 
900   /**
901    * getPiOrbitalTorsionType
902    *
903    * @param key a {@link java.lang.String} object.
904    * @return a {@link PiOrbitalTorsionType} object.
905    */
906   public PiOrbitalTorsionType getPiOrbitalTorsionType(String key) {
907     PiOrbitalTorsionType piOrbitalTorsionType = piOrbitalTorsionTypes.get(key);
908     if (piOrbitalTorsionType != null) {
909       piOrbitalTorsionType.piTorsUnit = getDouble("PITORSUNIT",
910           PiOrbitalTorsionType.DEFAULT_PITORS_UNIT);
911     }
912     return piOrbitalTorsionType;
913   }
914 
915   /**
916    * getPiOrbitalTorsionType
917    *
918    * @param a1 AtomType of atom 1.
919    * @param a2 AtomType of atom 2.
920    * @return {@link PiOrbitalTorsionType} object.
921    */
922   public PiOrbitalTorsionType getPiOrbitalTorsionType(AtomType a1, AtomType a2) {
923     int[] c = new int[2];
924     c[0] = a1.atomClass;
925     c[1] = a2.atomClass;
926     String key = PiOrbitalTorsionType.sortKey(c);
927     return getPiOrbitalTorsionType(key);
928   }
929 
930   /**
931    * getPolarizeType
932    *
933    * @param key a {@link java.lang.String} object.
934    * @return a {@link ffx.potential.parameters.PolarizeType} object.
935    */
936   public PolarizeType getPolarizeType(String key) {
937     return polarizeTypes.get(key);
938   }
939 
940   /**
941    * Getter for the field <code>properties</code>.
942    *
943    * @return a {@link org.apache.commons.configuration2.CompositeConfiguration} object.
944    */
945   public CompositeConfiguration getProperties() {
946     return properties;
947   }
948 
949   /**
950    * Getter for the field <code>relativeSolvationTypes</code>.
951    *
952    * @return a {@link java.util.HashMap} object.
953    */
954   public HashMap<String, RelativeSolvationType> getRelativeSolvationTypes() {
955     HashMap<String, RelativeSolvationType> types = new HashMap<>();
956     for (String key : relativeSolvationTypes.keySet()) {
957       types.put(key, relativeSolvationTypes.get(key));
958     }
959     return types;
960   }
961 
962   /**
963    * Get a SoluteType.
964    *
965    * @param key The class of the atom.
966    * @return The SoluteType if its present.
967    */
968   public SoluteType getSoluteType(String key) {
969     return soluteTypes.get(key);
970   }
971 
972   public Map<String, SoluteType> getSoluteTypes() {
973     return soluteTypes;
974   }
975 
976   /**
977    * getStretchBendType
978    *
979    * @param key a {@link java.lang.String} object.
980    * @return a {@link ffx.potential.parameters.StretchBendType} object.
981    */
982   public StretchBendType getStretchBendType(String key) {
983     StretchBendType stretchBendType = stretchBendTypes.get(key);
984     if (stretchBendType != null) {
985       stretchBendType.strbndunit = getDouble("STRBNDUNIT", StretchBendType.DEFAULT_STRBND_UNIT);
986     }
987     return stretchBendType;
988   }
989 
990   /**
991    * getStretchBendType
992    *
993    * @param a1 First AtomType.
994    * @param a2 Second AtomType.
995    * @param a3 Third AtomType.
996    * @return a {@link ffx.potential.parameters.StretchBendType} object.
997    */
998   public StretchBendType getStretchBendType(AtomType a1, AtomType a2, AtomType a3) {
999     int[] c = {a1.atomClass, a2.atomClass, a3.atomClass};
1000     String key = AngleType.sortKey(c);
1001     return getStretchBendType(key);
1002   }
1003 
1004   /**
1005    * getStretchTorsionType
1006    *
1007    * @param key a {@link java.lang.String} object.
1008    * @return a {@link ffx.potential.parameters.StretchTorsionType} object.
1009    */
1010   public StretchTorsionType getStretchTorsionType(String key) {
1011     StretchTorsionType stretchTorsionType = stretchTorsionTypes.get(key);
1012     if (stretchTorsionType != null) {
1013       stretchTorsionType.strTorUnit = getDouble("STRTORUNIT",
1014           StretchTorsionType.DEFAULT_STRTOR_UNIT);
1015     }
1016     return stretchTorsionType;
1017   }
1018 
1019   /**
1020    * getBoolean
1021    *
1022    * @param property The property to return.
1023    * @return a String.
1024    * @throws java.lang.Exception if any.
1025    */
1026   public String getString(String property) throws Exception {
1027     if (property == null) {
1028       throw new Exception("NULL property");
1029     }
1030     String key = toPropertyForm(property);
1031     if (!properties.containsKey(key)) {
1032       throw new Exception("Undefined property: " + key);
1033     }
1034     return properties.getString(key);
1035   }
1036 
1037   /**
1038    * getBoolean
1039    *
1040    * @param property      The property to return.
1041    * @param defaultString The default to return.
1042    * @return a boolean.
1043    */
1044   public String getString(String property, String defaultString) {
1045     try {
1046       return getString(property);
1047     } catch (Exception e) {
1048       return defaultString;
1049     }
1050   }
1051 
1052   /**
1053    * getTorsionTorsionType
1054    *
1055    * @param key a {@link java.lang.String} object.
1056    * @return a {@link ffx.potential.parameters.TorsionTorsionType} object.
1057    */
1058   public TorsionTorsionType getTorsionTorsionType(String key) {
1059     TorsionTorsionType torsionTorsionType = torsionTorsionTypes.get(key);
1060     if (torsionTorsionType != null) {
1061       torsionTorsionType.torTorUnit = getDouble("TORTORUNIT",
1062           TorsionTorsionType.DEFAULT_TORTOR_UNIT);
1063     }
1064     return torsionTorsionType;
1065   }
1066 
1067   /**
1068    * getTorsionType
1069    *
1070    * @param key a {@link java.lang.String} object.
1071    * @return a {@link ffx.potential.parameters.TorsionType} object.
1072    */
1073   public TorsionType getTorsionType(String key) {
1074     TorsionType torsionType = torsionTypes.get(key);
1075     if (torsionType != null) {
1076       torsionType.torsionUnit = getDouble("TORSIONUNIT", TorsionType.DEFAULT_TORSION_UNIT);
1077     }
1078     return torsionType;
1079   }
1080 
1081   /**
1082    * Find a torsion based on the specified classes.
1083    *
1084    * @param c0 Atom class 0.
1085    * @param c1 Atom class 1.
1086    * @param c2 Atom class 2.
1087    * @param c3 Atom class 3.
1088    * @return A torsion type if it exists.
1089    */
1090   private TorsionType getTorsionType(int c0, int c1, int c2, int c3) {
1091     int[] c = {c0, c1, c2, c3};
1092     String key = TorsionType.sortKey(c);
1093     return getTorsionType(key);
1094   }
1095 
1096   /**
1097    * getTorsionType
1098    *
1099    * @param a0 First AtomType.
1100    * @param a1 Second AtomType.
1101    * @param a2 Third AtomType.
1102    * @param a3 Fourth AtomType.
1103    * @return a {@link ffx.potential.parameters.TorsionType} object.
1104    */
1105   public TorsionType getTorsionType(AtomType a0, AtomType a1, AtomType a2, AtomType a3) {
1106     int c0 = a0.atomClass;
1107     int c1 = a1.atomClass;
1108     int c2 = a2.atomClass;
1109     int c3 = a3.atomClass;
1110 
1111     TorsionType torsionType = getTorsionType(c0, c1, c2, c3);
1112 
1113     // Single wild card.
1114     if (torsionType == null) {
1115       if (c0 > c3) {
1116         torsionType = getTorsionType(c0, c1, c2, 0);
1117         if (torsionType == null) {
1118           torsionType = getTorsionType(0, c1, c2, c3);
1119         }
1120       } else {
1121         torsionType = getTorsionType(0, c1, c2, c3);
1122         if (torsionType == null) {
1123           torsionType = getTorsionType(c0, c1, c2, 0);
1124         }
1125       }
1126     }
1127 
1128     // Double wild card.
1129     if (torsionType == null) {
1130       torsionType = getTorsionType(0, c1, c2, 0);
1131     }
1132 
1133     return torsionType;
1134   }
1135 
1136   /**
1137    * getUreyBradleyType
1138    *
1139    * @param key a {@link java.lang.String} object.
1140    * @return a {@link ffx.potential.parameters.UreyBradleyType} object.
1141    */
1142   public UreyBradleyType getUreyBradleyType(String key) {
1143     UreyBradleyType ureyBradleyType = ureyBradleyTypes.get(key);
1144     if (ureyBradleyType != null) {
1145       ureyBradleyType.ureyUnit = getDouble("UREYUNIT", UreyBradleyType.DEFAULT_UREY_UNIT);
1146       ureyBradleyType.cubic = getDouble("UREY_CUBIC", UreyBradleyType.DEFAULT_UREY_CUBIC);
1147       ureyBradleyType.quartic = getDouble("UREY_QUARTIC", UreyBradleyType.DEFAULT_UREY_QUARTIC);
1148     }
1149     return ureyBradleyType;
1150   }
1151 
1152   /**
1153    * getVDW14Type
1154    *
1155    * @param key a {@link java.lang.String} object.
1156    * @return a {@link ffx.potential.parameters.VDWType} object.
1157    */
1158   public VDWType getVDW14Type(String key) {
1159     return vanderWaals14Types.get(key);
1160   }
1161 
1162   /**
1163    * getVDW14Types
1164    *
1165    * @return a {@link java.util.Map} object.
1166    */
1167   public Map<String, VDWType> getVDW14Types() {
1168     return vanderWaals14Types;
1169   }
1170 
1171   /**
1172    * getVDWType
1173    *
1174    * @param key a {@link java.lang.String} object.
1175    * @return a {@link ffx.potential.parameters.VDWType} object.
1176    */
1177   public VDWType getVDWType(String key) {
1178     return vanderWaalsTypes.get(key);
1179   }
1180 
1181   /**
1182    * getVDWPairType
1183    *
1184    * @param key a {@link java.lang.String} object.
1185    * @return a {@link ffx.potential.parameters.VDWPairType} object.
1186    */
1187   public VDWPairType getVDWPairType(String key) {
1188     return vanderWaalsPairTypes.get(key);
1189   }
1190 
1191   /**
1192    * getVDWTypes
1193    *
1194    * @return a {@link java.util.Map} object.
1195    */
1196   public Map<String, VDWType> getVDWTypes() {
1197     return vanderWaalsTypes;
1198   }
1199 
1200   /**
1201    * getVDWPairTypes
1202    *
1203    * @return a {@link java.util.Map} object.
1204    */
1205   public Map<String, VDWPairType> getVDWPairTypes() {
1206     return vanderWaalsPairTypes;
1207   }
1208 
1209   /**
1210    * Checks if a property was specified.
1211    *
1212    * @param property String to check.
1213    * @return Returns true if the property has been specified.
1214    */
1215   public boolean hasProperty(String property) {
1216     if (property == null) {
1217       return false;
1218     }
1219     String key = toPropertyForm(property);
1220     return properties.containsKey(key);
1221   }
1222 
1223   /**
1224    * log
1225    */
1226   public void log() {
1227     for (ForceFieldType s : forceFieldTypes.keySet()) {
1228       log(s.toString());
1229     }
1230   }
1231 
1232   /**
1233    * Prints any force field keyword to Standard.out.
1234    *
1235    * @param key String
1236    */
1237   public void log(String key) {
1238     ForceFieldType type = ForceFieldType.valueOf(key);
1239     logger.info(toString(type));
1240   }
1241 
1242   /**
1243    * print
1244    */
1245   public void print() {
1246     for (ForceFieldType s : forceFieldTypes.keySet()) {
1247       print(s.toString());
1248     }
1249   }
1250 
1251   /**
1252    * print
1253    *
1254    * @param key a {@link java.lang.String} object.
1255    */
1256   public void print(String key) {
1257     ForceFieldType type = ForceFieldType.valueOf(key);
1258     logger.info(toString(type));
1259   }
1260 
1261   /**
1262    * Renumber ForceField class, type and biotype values.
1263    *
1264    * @param classOffset   The class offset.
1265    * @param typeOffset    The type offset.
1266    * @param bioTypeOffset The biotype offset.
1267    */
1268   public void renumberForceField(int classOffset, int typeOffset, int bioTypeOffset) {
1269     for (AngleType angleType : angleTypes.values()) {
1270       angleType.incrementClasses(classOffset);
1271     }
1272 
1273     for (AngleType angleType : anglepTypes.values()) {
1274       angleType.incrementClasses(classOffset);
1275     }
1276 
1277     for (AtomType atomType : atomTypes.values()) {
1278       atomType.incrementClassAndType(classOffset, typeOffset);
1279     }
1280 
1281     for (BioType bioType : bioTypes.values()) {
1282       bioType.incrementIndexAndType(bioTypeOffset, typeOffset);
1283     }
1284 
1285     for (BondType bondType : bondTypes.values()) {
1286       bondType.incrementClasses(classOffset);
1287     }
1288 
1289     for (MultipoleType multipoleType : multipoleTypes.values()) {
1290       multipoleType.incrementType(typeOffset);
1291     }
1292 
1293     for (OutOfPlaneBendType outOfPlaneBendType : outOfPlaneBendTypes.values()) {
1294       outOfPlaneBendType.incrementClasses(classOffset);
1295     }
1296 
1297     for (PiOrbitalTorsionType piOrbitalTorsionType : piOrbitalTorsionTypes.values()) {
1298       piOrbitalTorsionType.incrementClasses(classOffset);
1299     }
1300 
1301     for (PolarizeType polarizeType : polarizeTypes.values()) {
1302       polarizeType.incrementType(typeOffset);
1303     }
1304 
1305     for (StretchBendType stretchBendType : stretchBendTypes.values()) {
1306       stretchBendType.incrementClasses(classOffset);
1307     }
1308 
1309     for (StretchTorsionType stretchTorsionType : stretchTorsionTypes.values()) {
1310       stretchTorsionType.incrementClasses(classOffset);
1311     }
1312 
1313     for (AngleTorsionType angleTorsionType : angleTorsionTypes.values()) {
1314       angleTorsionType.incrementClasses(classOffset);
1315     }
1316 
1317     for (TorsionTorsionType torsionTorsionType : torsionTorsionTypes.values()) {
1318       torsionTorsionType.incrementClasses(classOffset);
1319     }
1320 
1321     for (TorsionType torsionType : torsionTypes.values()) {
1322       torsionType.incrementClasses(classOffset);
1323     }
1324 
1325     for (TorsionType torsionType : improperTypes.values()) {
1326       torsionType.incrementClasses(classOffset);
1327     }
1328 
1329     for (ImproperTorsionType improperTorsionType : imptorsTypes.values()) {
1330       improperTorsionType.incrementClasses(classOffset);
1331     }
1332 
1333     for (UreyBradleyType ureyBradleyType : ureyBradleyTypes.values()) {
1334       ureyBradleyType.incrementClasses(classOffset);
1335     }
1336 
1337     for (VDWType vanderWaalsType : vanderWaalsTypes.values()) {
1338       vanderWaalsType.incrementClass(classOffset);
1339     }
1340 
1341     for (VDWType vanderWaals14Type : vanderWaals14Types.values()) {
1342       vanderWaals14Type.incrementClass(classOffset);
1343     }
1344 
1345     for (VDWPairType vanderWaalsPairType : vanderWaalsPairTypes.values()) {
1346       vanderWaalsPairType.incrementClasses(classOffset);
1347     }
1348 
1349     for (SoluteType soluteType : soluteTypes.values()) {
1350       soluteType.incrementType(typeOffset);
1351     }
1352   }
1353 
1354   /**
1355    * The AngleFunction in use by this ForceField.
1356    *
1357    * @param angleFunction The AngleFunction to use.
1358    */
1359   public void setAngleFunction(AngleType.AngleFunction angleFunction) {
1360     for (AngleType angleType : anglepTypes.values()) {
1361       angleType.setAngleFunction(angleFunction);
1362     }
1363     for (AngleType angleType : angleTypes.values()) {
1364       angleType.setAngleFunction(angleFunction);
1365     }
1366   }
1367 
1368   /**
1369    * The BondFunction in use by this ForceField.
1370    *
1371    * @param bondFunction The BondFunction to use.
1372    */
1373   public void setBondFunction(BondType.BondFunction bondFunction) {
1374     for (BondType bondType : bondTypes.values()) {
1375       bondType.setBondFunction(bondFunction);
1376     }
1377   }
1378 
1379   /**
1380    * Return a String for any Force Field keyword.
1381    *
1382    * @param type ForceFieldType
1383    * @return String
1384    */
1385   public String toString(ForceFieldType type) {
1386     StringBuilder sb = new StringBuilder("\n");
1387     Map<String, ? extends BaseType> t = forceFieldTypes.get(type);
1388 
1389     if (t.isEmpty()) {
1390       return "";
1391     }
1392 
1393     for (Object o : t.values()) {
1394       sb.append(o.toString()).append("\n");
1395     }
1396     return sb.toString();
1397   }
1398 
1399   /**
1400    * {@inheritDoc}
1401    */
1402   @Override
1403   public String toString() {
1404     String forceFieldName;
1405     try {
1406       forceFieldName = getString("FORCEFIELD");
1407     } catch (Exception ex) {
1408       forceFieldName = "Unknown";
1409     }
1410     return forceFieldName;
1411   }
1412 
1413   /**
1414    * toString
1415    *
1416    * @param key a {@link java.lang.String} object.
1417    * @return a {@link java.lang.String} object.
1418    */
1419   public String toString(String key) {
1420     if (key == null) {
1421       return null;
1422     }
1423 
1424     key = toPropertyForm(key);
1425 
1426     if (properties.containsKey(key)) {
1427       List<Object> l = properties.getList(key);
1428       return key + " " + Arrays.toString(l.toArray());
1429     } else {
1430       return key + " is not defined.";
1431     }
1432   }
1433 
1434   /**
1435    * toStringBuffer
1436    *
1437    * @return Returns a StringBuffer representation of the ForceField.
1438    */
1439   public StringBuffer toStringBuffer() {
1440     StringBuffer sb = new StringBuffer();
1441     for (ForceFieldType s : forceFieldTypes.keySet()) {
1442       ForceFieldType type = ForceFieldType.valueOf(s.toString());
1443       sb.append(toString(type));
1444     }
1445     return sb;
1446   }
1447 
1448   /**
1449    * All atoms whose atomName begins with <code>name</code> for the given molecule will be updated to
1450    * the new type. For an atomName such as CD, it will map to both CD1 and CD2.
1451    *
1452    * @param molecule The molecule name.
1453    * @param atom     The atom name.
1454    * @param newType  The new atom type.
1455    * @return The AtomType that was replaced.
1456    */
1457   private AtomType updateBioType(String molecule, String atom, int newType) {
1458     int oldType = 0;
1459     for (BioType bioType : bioTypes.values()) {
1460       if (bioType.moleculeName.equalsIgnoreCase(molecule)) {
1461         if (atom.length() <= bioType.atomName.length()) {
1462           if (bioType.atomName.toUpperCase().startsWith(atom.toUpperCase())) {
1463             oldType = bioType.atomType;
1464             bioType.atomType = newType;
1465           }
1466         }
1467       }
1468     }
1469     return getAtomType(Integer.toString(oldType));
1470   }
1471 
1472   /**
1473    * Patches that add new atom classes/types that bond to existing atom classes/types require
1474    * "hybrid" force field types that include a mixture of new and existing types.
1475    *
1476    * @param typeMap    A look-up from new types to existing types.
1477    * @param patchTypes a {@link java.util.HashMap} object.
1478    */
1479   private void patchClassesAndTypes(HashMap<AtomType, AtomType> typeMap,
1480                                     HashMap<String, AtomType> patchTypes) {
1481 
1482     for (BondType bondType : bondTypes.values().toArray(new BondType[0])) {
1483       BondType newType = bondType.patchClasses(typeMap);
1484       if (newType != null) {
1485         logger.info(" " + newType);
1486         addForceFieldType(newType);
1487       }
1488     }
1489 
1490     for (AngleType angleType : angleTypes.values().toArray(new AngleType[0])) {
1491       AngleType newType = angleType.patchClasses(typeMap);
1492       if (newType != null) {
1493         logger.info(" " + newType);
1494         addForceFieldType(newType);
1495       }
1496     }
1497 
1498     for (OutOfPlaneBendType outOfPlaneBendType : outOfPlaneBendTypes.values()
1499         .toArray(new OutOfPlaneBendType[0])) {
1500       OutOfPlaneBendType newType = outOfPlaneBendType.patchClasses(typeMap);
1501       if (newType != null) {
1502         logger.info(" " + newType);
1503         addForceFieldType(newType);
1504       }
1505     }
1506 
1507     for (PiOrbitalTorsionType piOrbitalTorsionType : piOrbitalTorsionTypes.values()
1508         .toArray(new PiOrbitalTorsionType[0])) {
1509       PiOrbitalTorsionType newType = piOrbitalTorsionType.patchClasses(typeMap);
1510       if (newType != null) {
1511         logger.info(" " + newType);
1512         addForceFieldType(newType);
1513       }
1514     }
1515 
1516     for (StretchBendType stretchBendType : stretchBendTypes.values()
1517         .toArray(new StretchBendType[0])) {
1518       StretchBendType newType = stretchBendType.patchClasses(typeMap);
1519       if (newType != null) {
1520         logger.info(" " + newType);
1521         addForceFieldType(newType);
1522       }
1523     }
1524 
1525     /* for (TorsionTorsionType torsionTorsionType :
1526      * torsionTorsionTypes.values().toArray(new TorsionTorsionType[0])) {
1527      * String currentKey = torsionTorsionType.key;
1528      * torsionTorsionType.patchClasses(typeMap); if
1529      * (!torsionTorsionType.key.equals(currentKey)) {
1530      * torsionTorsionTypes.remove(currentKey);
1531      * addForceFieldType(torsionTorsionType); } }
1532      */
1533 
1534     for (TorsionType torsionType : torsionTypes.values().toArray(new TorsionType[0])) {
1535       TorsionType newType = torsionType.patchClasses(typeMap);
1536       if (newType != null) {
1537         logger.info(" " + newType);
1538         addForceFieldType(newType);
1539       }
1540     }
1541 
1542     /*
1543     for (ImproperTorsionType improperType : imptorsTypes.values().toArray(new ImproperTorsionType[0])) {
1544         String currentKey = improperType.key;
1545         improperType.patchClasses(typeMap);
1546         if (!improperType.key.equals(currentKey)) {
1547             torsionTypes.remove(currentKey);
1548             addForceFieldType(improperType);
1549         }
1550     }
1551 
1552     for (UreyBradleyType ureyBradleyType : ureyBradleyTypes.values().toArray(new UreyBradleyType[0])) {
1553         String currentKey = ureyBradleyType.key;
1554         ureyBradleyType.patchClasses(typeMap);
1555         if (!ureyBradleyType.key.equals(currentKey)) {
1556             ureyBradleyTypes.remove(currentKey);
1557             addForceFieldType(ureyBradleyType);
1558         }
1559     } */
1560 
1561     for (MultipoleType multipoleType : multipoleTypes.values().toArray(new MultipoleType[0])) {
1562       MultipoleType newType = multipoleType.patchTypes(typeMap);
1563       if (newType != null) {
1564         logger.info(" " + newType);
1565         addForceFieldType(newType);
1566       }
1567     }
1568 
1569     try {
1570       for (AtomType atomType : patchTypes.values()) {
1571         PolarizeType polarizeType = getPolarizeType(atomType.key);
1572         if (polarizeType != null && polarizeType.patchTypes(typeMap)) {
1573           logger.info(" " + polarizeType);
1574         }
1575       }
1576     } catch (Exception e) {
1577       // Inefficient hack. Should actually check if polarizeTypes are necessary.
1578     }
1579   }
1580 
1581   /**
1582    * Returns the minimum atom class value.
1583    *
1584    * @return The minimum atom class value.
1585    */
1586   private int minClass() {
1587     int minClass = maxClass();
1588     for (AtomType type : atomTypes.values()) {
1589       if (type.atomClass < minClass) {
1590         minClass = type.atomClass;
1591       }
1592     }
1593     return minClass;
1594   }
1595 
1596   /**
1597    * Returns the minimum atom type value.
1598    *
1599    * @return The minimum atom type value.
1600    */
1601   private int minType() {
1602     int minType = maxType();
1603     for (String key : atomTypes.keySet()) {
1604       int type = Integer.parseInt(key);
1605       if (type < minType) {
1606         minType = type;
1607       }
1608     }
1609     return minType;
1610   }
1611 
1612   /**
1613    * Returns the minimum Biotype value.
1614    *
1615    * @return The minimum Biotype value.
1616    */
1617   private int minBioType() {
1618     int minBioType = maxBioType();
1619     for (String key : bioTypes.keySet()) {
1620       int type = Integer.parseInt(key);
1621       if (type < minBioType) {
1622         minBioType = type;
1623       }
1624     }
1625     return minBioType;
1626   }
1627 
1628   /**
1629    * Returns the maximum atom class value.
1630    *
1631    * @return The maximum atom class value.
1632    */
1633   private int maxClass() {
1634     int maxClass = 0;
1635     for (AtomType type : atomTypes.values()) {
1636       if (type.atomClass > maxClass) {
1637         maxClass = type.atomClass;
1638       }
1639     }
1640     return maxClass;
1641   }
1642 
1643   /**
1644    * Returns the maximum atom type value.
1645    *
1646    * @return The maximum atom type value.
1647    */
1648   private int maxType() {
1649     int maxType = 0;
1650     for (String key : atomTypes.keySet()) {
1651       int type = Integer.parseInt(key);
1652       if (type > maxType) {
1653         maxType = type;
1654       }
1655     }
1656     return maxType;
1657   }
1658 
1659   /**
1660    * Returns the maximum Biotype.
1661    *
1662    * @return The maximum Biotype.
1663    */
1664   private int maxBioType() {
1665     int maxBioType = 0;
1666     for (String key : bioTypes.keySet()) {
1667       int type = Integer.parseInt(key);
1668       if (type > maxBioType) {
1669         maxBioType = type;
1670       }
1671     }
1672     return maxBioType;
1673   }
1674 
1675   private void modifiedResidue(String modres) {
1676     String[] tokens = modres.trim().split(" +");
1677     String modResname = tokens[0].toUpperCase();
1678     String stdName = tokens[1].toUpperCase();
1679     HashMap<String, AtomType> patchAtomTypes = getAtomTypes(modResname);
1680     HashMap<String, AtomType> stdAtomTypes = getAtomTypes(stdName);
1681 
1682     HashMap<String, AtomType> patchTypes = new HashMap<>();
1683     int len = tokens.length;
1684     for (int i = 2; i < len; i++) {
1685       String atomName = tokens[i].toUpperCase();
1686       if (!patchTypes.containsKey(atomName) && patchAtomTypes.containsKey(atomName)) {
1687         AtomType type = patchAtomTypes.get(atomName);
1688         patchTypes.put(atomName, type);
1689       }
1690     }
1691 
1692     HashMap<AtomType, AtomType> typeMap = new HashMap<>();
1693     for (String atomName : stdAtomTypes.keySet()) {
1694       boolean found = false;
1695       for (int i = 2; i < len; i++) {
1696         if (atomName.equalsIgnoreCase(tokens[i])) {
1697           found = true;
1698           break;
1699         }
1700       }
1701       if (!found) {
1702         AtomType stdType = stdAtomTypes.get(atomName);
1703         // Edit new BioType records to point to an existing force field type.
1704         AtomType patchType = updateBioType(modResname, atomName, stdType.type);
1705         if (patchType != null) {
1706           typeMap.put(patchType, stdType);
1707           logger.info(" " + patchType + " -> " + stdType);
1708         }
1709       }
1710     }
1711 
1712     patchClassesAndTypes(typeMap, patchTypes);
1713   }
1714 
1715   /**
1716    * If some set of other boolean values imply another boolean is true, set that implied boolean to
1717    * true.
1718    *
1719    * <p>First designed for LAMBDATERM, which is implied by any of VDW_LAMBDATERM, ELEC_LAMBDATERM,
1720    * or GK_LAMBDATERM.
1721    *
1722    * @param toSet         Property to set true if otherBooleans true.
1723    * @param otherBooleans Properties that imply toSet is true.
1724    */
1725   private void trueImpliedBoolean(String toSet, String... otherBooleans) {
1726     // Short-circuit if it's already true.
1727     if (getBoolean(toSet, false)) {
1728       return;
1729     }
1730     // Check all the other booleans that imply toSet.
1731     for (String otherBool : otherBooleans) {
1732       if (getBoolean(otherBool, false)) {
1733         addProperty(toSet, "true");
1734         logger.info(format(" Setting implied boolean %s true due to boolean %s", toSet, otherBool));
1735       }
1736     }
1737   }
1738 
1739   /**
1740    * Check for self-consistent polarization groups.
1741    */
1742   private void checkPolarizationTypes() {
1743     boolean change = false;
1744     for (String key : polarizeTypes.keySet()) {
1745       PolarizeType polarizeType = polarizeTypes.get(key);
1746       int orig = Integer.parseInt(key);
1747       int[] types = polarizeType.polarizationGroup;
1748       if (types == null) {
1749         continue;
1750       }
1751 
1752       for (int type : types) {
1753         String key2 = Integer.toString(type);
1754         PolarizeType polarizeType2 = polarizeTypes.get(key2);
1755         if (polarizeType2 == null) {
1756           logger.severe(
1757               format("Polarize type %s references nonexistant polarize type %s.", key, key2));
1758           continue;
1759         }
1760         int[] types2 = polarizeType2.polarizationGroup;
1761         if (types2 == null) {
1762           polarizeType2.add(orig);
1763           change = true;
1764           continue;
1765         }
1766         boolean found = false;
1767         for (int type2 : types2) {
1768           for (int type3 : types) {
1769             if (type2 == type3) {
1770               found = true;
1771               break;
1772             }
1773           }
1774           if (!found) {
1775             polarizeType.add(type2);
1776             change = true;
1777           }
1778         }
1779       }
1780     }
1781     if (change) {
1782       checkPolarizationTypes();
1783     }
1784   }
1785 
1786   public enum ELEC_FORM {
1787     PAM, FIXED_CHARGE
1788   }
1789 
1790   /**
1791    * Available force fields.
1792    */
1793   public enum ForceFieldName {
1794     AMBER_1994, AMBER_1996, AMBER_1998, AMBER_1999, AMBER_1999_SB, AMOEBA_2004, AMOEBA_2009, AMOEBA_BIO_2009, AMOEBA_BIO_2018, AMOEBA_BIO_2018_CPHMD, AMOEBA_NUC_2017, AMOEBA_PROTEIN_2004, AMOEBA_PROTEIN_2013, AMOEBA_WATER_2003, AMOEBA_WATER_2014, CHARMM_22, CHARMM_22_CMAP, IAMOEBA_WATER, OPLS_AA, OPLS_AAL
1795   }
1796 
1797   public enum ForceFieldType {
1798     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
1799   }
1800 
1801 }