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