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