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