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