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.parsers;
39  
40  import ffx.potential.parameters.AngleTorsionType;
41  import ffx.potential.parameters.AngleType;
42  import ffx.potential.parameters.AtomType;
43  import ffx.potential.parameters.BaseType;
44  import ffx.potential.parameters.BioType;
45  import ffx.potential.parameters.BondType;
46  import ffx.potential.parameters.ForceField;
47  import ffx.potential.parameters.ForceField.ForceFieldName;
48  import ffx.potential.parameters.ForceField.ForceFieldType;
49  import ffx.potential.parameters.ImproperTorsionType;
50  import ffx.potential.parameters.MultipoleType;
51  import ffx.potential.parameters.OutOfPlaneBendType;
52  import ffx.potential.parameters.PiOrbitalTorsionType;
53  import ffx.potential.parameters.PolarizeType;
54  import ffx.potential.parameters.RelativeSolvationType;
55  import ffx.potential.parameters.SoluteType;
56  import ffx.potential.parameters.StretchBendType;
57  import ffx.potential.parameters.StretchTorsionType;
58  import ffx.potential.parameters.TorsionTorsionType;
59  import ffx.potential.parameters.TorsionType;
60  import ffx.potential.parameters.UreyBradleyType;
61  import ffx.potential.parameters.VDWPairType;
62  import ffx.potential.parameters.VDWType;
63  import ffx.utilities.Keyword;
64  import org.apache.commons.configuration2.CompositeConfiguration;
65  import org.apache.commons.configuration2.Configuration;
66  import org.apache.commons.configuration2.PropertiesConfiguration;
67  import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
68  import org.apache.commons.configuration2.builder.fluent.Parameters;
69  import org.apache.commons.configuration2.ex.ConfigurationException;
70  
71  import java.io.BufferedReader;
72  import java.io.File;
73  import java.io.FileInputStream;
74  import java.io.FileNotFoundException;
75  import java.io.IOException;
76  import java.io.InputStream;
77  import java.io.InputStreamReader;
78  import java.net.URL;
79  import java.util.Iterator;
80  import java.util.logging.Level;
81  import java.util.logging.Logger;
82  
83  import static ffx.potential.parameters.AngleType.AngleFunction;
84  import static ffx.potential.parameters.BondType.BondFunction;
85  import static java.lang.String.format;
86  
87  /**
88   * The ForceFieldFilter Class is used to parse and store molecular mechanics data from
89   * keyword/property and parameter (*.PRM) files.
90   *
91   * <p>Alternatively, an Apache Commons "Configuration" instance can be parsed.
92   *
93   * @author Michael J. Schnieders
94   * @since 1.0
95   */
96  public class ForceFieldFilter {
97  
98    private static final Logger logger = Logger.getLogger(ForceFieldFilter.class.getName());
99  
100   /** Default internal force field. */
101   private static final ForceFieldName DEFAULT_FORCE_FIELD = ForceFieldName.AMOEBA_BIO_2018;
102   /** An external force field parameter file. */
103   private final File forceFieldFile;
104   /** A CompositeConfiguration instance to search for force field types. */
105   private final CompositeConfiguration properties;
106   /** The ForceField instance that will be returned. */
107   private final ForceField forceField;
108 
109   /**
110    * Constructor for ForceFieldFilter.
111    *
112    * @param properties a {@link org.apache.commons.configuration2.CompositeConfiguration}
113    *     object.
114    */
115   public ForceFieldFilter(CompositeConfiguration properties) {
116     this.properties = properties;
117     if (properties.containsKey("parameters")) {
118       String fileName = properties.getString("parameters");
119       if (properties.containsKey("propertyFile")) {
120         String propertyName = properties.getString("propertyFile");
121         logger.info(" Property File: " + propertyName);
122         File propertyFile = new File(propertyName);
123         forceFieldFile = parseParameterLocation(fileName, propertyFile);
124       } else {
125         forceFieldFile = parseParameterLocation(fileName, null);
126       }
127     } else {
128       forceFieldFile = null;
129     }
130     forceField = new ForceField(properties);
131   }
132 
133   /**
134    * Parse a Force Field parameter file and echo the results with slashes.
135    *
136    * @param args an array of {@link java.lang.String} objects.
137    */
138   public static void main(String[] args) {
139     if (args == null || args.length < 1) {
140       System.out.println("Usage: ForceFieldFilter <file.prm>");
141       System.exit(-1);
142     }
143     CompositeConfiguration properties = Keyword.loadProperties(null);
144     properties.setProperty("parameters", args[0]);
145     ForceFieldFilter forceFieldFilter = new ForceFieldFilter(properties);
146     ForceField forceField = forceFieldFilter.parse();
147     if (forceField != null) {
148       forceField.print();
149     }
150   }
151 
152   /**
153    * parseParameterLocation
154    *
155    * @param parameterLocation a {@link java.lang.String} object.
156    * @param keyFile a {@link java.io.File} object.
157    * @return a {@link java.io.File} object.
158    */
159   private static File parseParameterLocation(String parameterLocation, File keyFile) {
160     File parameterFile = null;
161     if (parameterLocation != null && !parameterLocation.equalsIgnoreCase("NONE")) {
162       // Remove quotes
163       parameterLocation = parameterLocation.replaceAll("\"", "");
164       // Append the suffix if necessary
165       parameterFile = new File(parameterLocation);
166       // If the location is not absolute, check if it is relative
167       // to the key file location.
168       if (!parameterFile.exists() && keyFile != null) {
169         parameterFile = new File(keyFile.getParent() + File.separator + parameterLocation);
170       }
171     }
172     return parameterFile;
173   }
174 
175   /**
176    * parse
177    *
178    * @return a {@link ffx.potential.parameters.ForceField} object.
179    */
180   public ForceField parse() {
181     try {
182       // Try to parse an external (i.e., not in the FFX jar) parameter file.
183       if (forceFieldFile != null) {
184         File fileToOpen = forceFieldFile;
185         if (!fileToOpen.exists()) {
186           fileToOpen = new File(forceFieldFile.getAbsolutePath() + ".prm");
187           if (!fileToOpen.exists()) {
188             logger.log(Level.INFO, " {0} does not exist.", forceFieldFile);
189             return null;
190           }
191         }
192         if (!fileToOpen.canRead()) {
193           logger.log(Level.INFO, " {0} can not be read.", fileToOpen);
194           return null;
195         }
196         parse(new FileInputStream(fileToOpen));
197       } else {
198         // Parse an internal parameter file and add it to the composite configuration.
199         String defaultFFstring = DEFAULT_FORCE_FIELD.toString().toUpperCase().replaceAll("_", "-");
200         String forceFieldString = properties.getString("forcefield", defaultFFstring);
201         ForceFieldName ff;
202         try {
203           ff = ForceField.ForceFieldName.valueOf(forceFieldString.toUpperCase().replace('-', '_'));
204         } catch (Exception e) {
205           logger.info(format(" The forcefield property %s was not recognized.\n", forceFieldString));
206           ff = DEFAULT_FORCE_FIELD;
207         }
208         URL url = ForceField.getForceFieldURL(ff);
209         if (url != null) {
210           forceField.forceFieldURL = url;
211           try {
212             FileBasedConfigurationBuilder<PropertiesConfiguration> builder = new FileBasedConfigurationBuilder<>(
213                 PropertiesConfiguration.class).configure(
214                 new Parameters().properties().setURL(url).setThrowExceptionOnMissing(true)
215                     // .setListDelimiterHandler(new DefaultListDelimiterHandler(','))
216                     .setIncludesAllowed(false));
217             PropertiesConfiguration forceFieldConfiguration = builder.getConfiguration();
218             String name = ForceField.toPropertyForm(ff.toString());
219             forceFieldConfiguration.setHeader("Internal force field (" + name + ").");
220             properties.addConfiguration(forceFieldConfiguration);
221           } catch (ConfigurationException e) {
222             logger.warning(e.toString());
223           }
224         }
225       }
226 
227       // Overwrite parameters of the forceFieldFile with those from the CompositeConfiguration.
228       if (properties != null) {
229         parse(properties);
230       }
231     } catch (FileNotFoundException e) {
232       String message = "Exception parsing force field.";
233       logger.log(Level.WARNING, message, e);
234     }
235 
236     String forceFieldName = forceField.getString("forcefield", "none");
237     if (forceFieldName != null) {
238       forceFieldName = forceFieldName.toUpperCase();
239       if (forceFieldName.contains("AMBER") || forceFieldName.contains("OPLS")
240           || forceFieldName.contains("CHARMM")) {
241         forceField.setBondFunction(BondFunction.HARMONIC);
242         forceField.setAngleFunction(AngleFunction.HARMONIC);
243       }
244     }
245 
246     return forceField;
247   }
248 
249   private void parse(CompositeConfiguration properties) {
250     try {
251       int numConfigs = properties.getNumberOfConfigurations();
252       /*
253        Loop over the configurations starting with the lowest precedence.
254        This way higher precedence entries will overwrite lower
255        precedence entries within the ForceField instance.
256       */
257       for (int n = numConfigs - 1; n >= 0; n--) {
258         Configuration config = properties.getConfiguration(n);
259         if (config instanceof PropertiesConfiguration propertiesConfiguration) {
260           if (propertiesConfiguration.getHeader() != null) {
261             logger.info(" Parsing: " + propertiesConfiguration.getHeader());
262           }
263         }else{
264           if(logger.isLoggable(Level.FINER)) {
265             logger.finer(" Configuration was not an instance of PropertiesConfiguration.");
266           }
267         }
268 
269         Iterator<String> i = config.getKeys();
270         while (i.hasNext()) {
271           String key = i.next();
272 
273           // If the key is not recognized as a force field keyword, continue to the next key.
274           if (!ForceField.isForceFieldType(key)) {
275             if(logger.isLoggable(Level.FINER)){
276               logger.finer(" Key value (" + key + ") is not a valid force field type.");
277             }
278             continue;
279           }
280 
281           String[] list = config.getStringArray(key);
282           for (String s : list) {
283             // Add back the key to the input line.
284             s = key + " " + s;
285 
286             // Split the line on the pound symbol to remove comments.
287             String input = s.split("#+")[0];
288             String[] tokens = input.trim().split(" +");
289 
290             // Parse force field types.
291             ForceFieldType type;
292             try {
293               type = ForceFieldType.valueOf(key.toUpperCase());
294             } catch (Exception e) {
295               break;
296             }
297 
298             BaseType baseType = switch (type) {
299               case ATOM -> AtomType.parse(input, tokens);
300               case ANGTORS -> AngleTorsionType.parse(input, tokens);
301               case ANGLE -> AngleType.parse(input, tokens);
302               case ANGLEP -> AngleType.parseInPlane(input, tokens);
303               case BIOTYPE -> BioType.parse(input, tokens);
304               case BOND -> BondType.parse(input, tokens);
305               case CHARGE -> MultipoleType.parseChargeType(input, tokens);
306               case MULTIPOLE -> MultipoleType.parse(input, tokens);
307               case OPBEND -> OutOfPlaneBendType.parse(input, tokens);
308               case STRBND -> StretchBendType.parse(input, tokens);
309               case PITORS -> PiOrbitalTorsionType.parse(input, tokens);
310               case IMPTORS -> ImproperTorsionType.parse(input, tokens);
311               case TORSION -> TorsionType.parse(input, tokens);
312               case IMPROPER -> TorsionType.parseImproper(input, tokens);
313               case STRTORS -> StretchTorsionType.parse(input, tokens);
314               case TORTORS -> TorsionTorsionType.parse(input, tokens);
315               case UREYBRAD -> UreyBradleyType.parse(input, tokens);
316               case VDW -> VDWType.parse(input, tokens);
317               case VDW14 -> VDWType.parseVDW14(input, tokens);
318               case VDWPR, VDWPAIR -> VDWPairType.parse(input, tokens);
319               case POLARIZE -> PolarizeType.parse(input, tokens);
320               case RELATIVESOLV -> RelativeSolvationType.parse(input, tokens);
321               case SOLUTE -> SoluteType.parse(input, tokens);
322               default -> {
323                 logger.log(Level.WARNING, "ForceField type recognized, but not stored:{0}", type);
324                 yield null;
325               }
326             };
327             if (baseType != null) {
328               forceField.addForceFieldType(baseType);
329             }
330           }
331         }
332       }
333     } catch (Exception e) {
334       String message = "Exception parsing force field.";
335       logger.log(Level.WARNING, message, e);
336     }
337     logger.info("");
338   }
339 
340   private void parse(InputStream stream) {
341     try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {
342       while (br.ready()) {
343         String input = br.readLine();
344         parse(input, br);
345       }
346     } catch (IOException e) {
347       String message = "Error parsing force field parameters.\n";
348       logger.log(Level.SEVERE, message, e);
349     }
350   }
351 
352   private void parse(String input, BufferedReader br) {
353 
354     // Split the line on the pound symbol to remove comments.
355     String originalInput = input;
356     String[] inputs = input.split("#");
357     if (inputs.length < 1) {
358       return;
359     }
360 
361     input = inputs[0].split("#")[0];
362     // Split the line into tokens between instances of 1 or more spaces.
363     String[] tokens = input.trim().split(" +");
364     if (tokens[0].equalsIgnoreCase("")) {
365       return;
366     }
367 
368     try {
369       ForceFieldType type = ForceFieldType.valueOf(tokens[0].toUpperCase());
370       BaseType baseType = switch (type) {
371         case ATOM -> AtomType.parse(input, tokens);
372         case ANGLE -> AngleType.parse(input, tokens);
373         case ANGLEP -> AngleType.parseInPlane(input, tokens);
374         case ANGTORS -> AngleTorsionType.parse(input, tokens);
375         case BIOTYPE -> BioType.parse(input, tokens);
376         case BOND -> BondType.parse(input, tokens);
377         case CHARGE -> MultipoleType.parseChargeType(input, tokens);
378         case MULTIPOLE -> MultipoleType.parse(input, tokens, br);
379         case OPBEND -> OutOfPlaneBendType.parse(input, tokens);
380         case STRBND -> StretchBendType.parse(input, tokens);
381         case PITORS -> PiOrbitalTorsionType.parse(input, tokens);
382         case IMPTORS -> ImproperTorsionType.parse(input, tokens);
383         case STRTORS -> StretchTorsionType.parse(input, tokens);
384         case TORSION -> TorsionType.parse(input, tokens);
385         case IMPROPER -> TorsionType.parseImproper(input, tokens);
386         case TORTORS -> TorsionTorsionType.parse(input, tokens, br);
387         case UREYBRAD -> UreyBradleyType.parse(input, tokens);
388         case VDW -> VDWType.parse(input, tokens);
389         case VDW14 -> VDWType.parseVDW14(input, tokens);
390         case VDWPR, VDWPAIR -> VDWPairType.parse(input, tokens);
391         case POLARIZE -> PolarizeType.parse(input, tokens);
392         case RELATIVESOLV -> RelativeSolvationType.parse(input, tokens);
393         case SOLUTE ->
394           // SOLUTE lines can't be split on #'s because of SMARTS strings syntax
395             SoluteType.parse(originalInput, originalInput.trim().split(" +"));
396         default -> {
397           logger.log(Level.WARNING, "ForceField type recognized, but not stored:{0}", type);
398           yield null;
399         }
400       };
401       if (baseType != null) {
402         forceField.addForceFieldType(baseType);
403       }
404       return;
405     } catch (Exception e) {
406       // Note -- this serves to skip blank lines in *.patch files but also hide an actual bug's
407       // exception
408       // String message = "Exception parsing force field parameters.\n";
409       // logger.log(Level.WARNING, message, e);
410     }
411 
412     // Otherwise -- add this entry as a property.
413     try {
414       String key = tokens[0];
415       String value = input.replaceFirst(tokens[0], "").trim();
416       forceField.addProperty(key, value);
417     } catch (Exception e) {
418       logger.info(" Ignored line: " + input);
419     }
420   }
421 }