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.utils;
39  
40  import ffx.potential.ForceFieldEnergy;
41  import ffx.potential.MolecularAssembly;
42  import ffx.potential.Utilities;
43  import ffx.potential.bonded.RotamerLibrary;
44  import ffx.potential.parameters.ForceField;
45  import ffx.potential.parsers.ARCFileFilter;
46  import ffx.potential.parsers.FileOpener;
47  import ffx.potential.parsers.ForceFieldFilter;
48  import ffx.potential.parsers.INTFileFilter;
49  import ffx.potential.parsers.INTFilter;
50  import ffx.potential.parsers.PDBFileFilter;
51  import ffx.potential.parsers.PDBFilter;
52  import ffx.potential.parsers.PDBFilter.Mutation;
53  import ffx.potential.parsers.SystemFilter;
54  import ffx.potential.parsers.XYZFileFilter;
55  import ffx.potential.parsers.XYZFilter;
56  import ffx.utilities.Keyword;
57  import org.apache.commons.configuration2.CompositeConfiguration;
58  import org.apache.commons.io.FilenameUtils;
59  
60  import java.io.File;
61  import java.io.IOException;
62  import java.nio.file.Path;
63  import java.nio.file.Paths;
64  import java.util.ArrayList;
65  import java.util.List;
66  import java.util.logging.Level;
67  import java.util.logging.Logger;
68  
69  import static java.lang.String.format;
70  
71  /**
72   * The PotentialsFileOpener class specifies a Runnable object which is constructed with a File and,
73   * when run, allows returning any opened MolecularAssembly objects and their associated properties.
74   *
75   * @author Jacob M. Litman
76   * @author Michael J. Schnieders
77   * @since 1.0
78   */
79  public class PotentialsFileOpener implements FileOpener {
80  
81    private static final Logger logger = Logger.getLogger(PotentialsFileOpener.class.getName());
82    private final File file;
83    private final Path filepath;
84    private final File[] allFiles;
85    private final Path[] allPaths;
86    private int nThreads = -1;
87    private final List<MolecularAssembly> assemblies;
88    private final List<CompositeConfiguration> propertyList;
89    // Presently, will just be the first element of assemblies.
90    private MolecularAssembly activeAssembly;
91    private CompositeConfiguration activeProperties;
92    private SystemFilter filter;
93    private List<Mutation> mutationsToApply;
94  
95    /**
96     * Constructor for PotentialsFileOpener.
97     *
98     * @param file a {@link java.io.File} object.
99     */
100   public PotentialsFileOpener(File file) {
101     if (!file.exists() || !file.isFile()) {
102       throw new IllegalArgumentException(
103           String.format(" File %s either did not exist or was not a file.", file.getName()));
104     }
105     this.file = file;
106     Path pwdPath;
107     Path absPath;
108     try {
109       pwdPath = Paths.get(new File("").getCanonicalPath());
110     } catch (IOException ex) {
111       pwdPath = Paths.get(new File("").getAbsolutePath());
112     }
113     try {
114       absPath = Paths.get(file.getCanonicalPath());
115     } catch (IOException ex) {
116       absPath = Paths.get(file.getAbsolutePath());
117     }
118     filepath = pwdPath.relativize(absPath);
119     allFiles = new File[1];
120     allFiles[0] = this.file;
121     allPaths = new Path[1];
122     allPaths[0] = this.filepath;
123     assemblies = new ArrayList<>();
124     propertyList = new ArrayList<>();
125   }
126 
127   /**
128    * Constructor for PotentialsFileOpener.
129    *
130    * @param filename a {@link java.lang.String} object.
131    */
132   PotentialsFileOpener(String filename) {
133     this(new File(filename));
134   }
135 
136   /**
137    * Constructor for PotentialsFileOpener.
138    *
139    * @param filenames an array of {@link java.lang.String} objects.
140    */
141   PotentialsFileOpener(String[] filenames) {
142     if (filenames == null) {
143       throw new IllegalArgumentException(" Array of files to be opened was null.");
144     }
145     int numFiles = filenames.length;
146     if (numFiles == 0) {
147       throw new IllegalArgumentException(" Array of files to be opened was empty.");
148     }
149 
150     List<File> fileList = new ArrayList<>();
151     List<Path> pathList = new ArrayList<>();
152     Path pwdPath;
153     try {
154       pwdPath = Paths.get(new File("").getCanonicalPath());
155     } catch (IOException ex) {
156       pwdPath = Paths.get(new File("").getAbsolutePath());
157     }
158     for (String filename : filenames) {
159       try {
160         File tryFile = new File(filename);
161         if (!(tryFile.exists() && tryFile.isFile())) {
162           continue;
163         }
164         Path absPath;
165         try {
166           absPath = Paths.get(tryFile.getCanonicalPath());
167         } catch (IOException ex) {
168           absPath = Paths.get(tryFile.getAbsolutePath());
169         }
170         Path thisPath = pwdPath.relativize(absPath);
171         fileList.add(tryFile);
172         pathList.add(thisPath);
173       } catch (Exception ex) {
174         // Simply continue.
175       }
176     }
177     int numAccepted = fileList.size();
178     if (numAccepted < 1) {
179       throw new IllegalArgumentException(" No valid files could be found to open.");
180     }
181     allFiles = fileList.toArray(new File[numAccepted]);
182     allPaths = pathList.toArray(new Path[numAccepted]);
183     this.file = allFiles[0];
184     this.filepath = allPaths[0];
185     assemblies = new ArrayList<>();
186     propertyList = new ArrayList<>();
187   }
188 
189   /**
190    * {@inheritDoc}
191    *
192    * <p>Returns all MolecularAssembly objects created by this opener.
193    */
194   @Override
195   public MolecularAssembly[] getAllAssemblies() {
196     return assemblies.toArray(new MolecularAssembly[0]);
197   }
198 
199   /**
200    * {@inheritDoc}
201    *
202    * <p>Returns the properties of all MolecularAssembly objects created by this opener.
203    */
204   @Override
205   public CompositeConfiguration[] getAllProperties() {
206     return propertyList.toArray(new CompositeConfiguration[0]);
207   }
208 
209   /**
210    * {@inheritDoc}
211    *
212    * <p>Returns the first MolecularAssembly created by the run() function.
213    */
214   @Override
215   public MolecularAssembly getAssembly() {
216     return activeAssembly;
217   }
218 
219   /**
220    * Getter for the field <code>filter</code>.
221    *
222    * @return a {@link ffx.potential.parsers.SystemFilter} object.
223    */
224   public SystemFilter getFilter() {
225     return filter;
226   }
227 
228   /**
229    * {@inheritDoc}
230    *
231    * <p>Returns the properties associated with the first MolecularAssembly.
232    */
233   @Override
234   public CompositeConfiguration getProperties() {
235     return activeProperties;
236   }
237 
238   /**
239    * {@inheritDoc}
240    *
241    * <p>At present, parses the PDB, XYZ, INT, or ARC file from the constructor and creates
242    * MolecularAssembly and properties objects.
243    */
244   @Override
245   public void run() {
246     int numFiles = allFiles.length;
247     for (int i = 0; i < numFiles; i++) {
248       File fileI = allFiles[i];
249       Path pathI = allPaths[i];
250       MolecularAssembly assembly = new MolecularAssembly(pathI.toString());
251       assembly.setFile(fileI);
252       CompositeConfiguration properties = Keyword.loadProperties(fileI);
253       ForceFieldFilter forceFieldFilter = new ForceFieldFilter(properties);
254       ForceField forceField = forceFieldFilter.parse();
255       String[] patches = properties.getStringArray("patch");
256       for (String patch : patches) {
257         logger.info(" Attempting to read force field patch from " + patch + ".");
258         CompositeConfiguration patchConfiguration = new CompositeConfiguration();
259         try {
260           patchConfiguration.addProperty("propertyFile", fileI.getCanonicalPath());
261         } catch (IOException e) {
262           logger.log(Level.INFO, " Error loading {0}.", patch);
263         }
264         patchConfiguration.addProperty("parameters", patch);
265         forceFieldFilter = new ForceFieldFilter(patchConfiguration);
266         ForceField patchForceField = forceFieldFilter.parse();
267         forceField.append(patchForceField);
268         if (RotamerLibrary.addRotPatch(patch)) {
269           logger.info(format(" Loaded rotamer definitions from patch %s.", patch));
270         }
271       }
272       assembly.setForceField(forceField);
273       if (new PDBFileFilter().acceptDeep(fileI)) {
274         filter = new PDBFilter(fileI, assembly, forceField, properties);
275       } else if (new XYZFileFilter().acceptDeep(fileI)) {
276         filter = new XYZFilter(fileI, assembly, forceField, properties);
277       } else if (new INTFileFilter().acceptDeep(fileI) || new ARCFileFilter().accept(fileI)) {
278         filter = new INTFilter(fileI, assembly, forceField, properties);
279       } else {
280         throw new IllegalArgumentException(
281             format(" File %s could not be recognized as a valid PDB, XYZ, INT, or ARC file.",
282                 pathI));
283       }
284 
285       /* If on-open mutations requested, add them to filter. */
286       if (mutationsToApply != null && !mutationsToApply.isEmpty()) {
287         if (!(filter instanceof PDBFilter)) {
288           throw new UnsupportedOperationException(
289               "Applying mutations during open only supported by PDB filter atm.");
290         }
291         ((PDBFilter) filter).mutate(mutationsToApply);
292       }
293 
294       if (filter.readFile()) {
295         if (!(filter instanceof PDBFilter)) {
296           Utilities.biochemistry(assembly, filter.getAtomList());
297         }
298         filter.applyAtomProperties();
299         assembly.finalize(true, forceField);
300         ForceFieldEnergy energy;
301         if (nThreads > 0) {
302           energy = ForceFieldEnergy.energyFactory(assembly, nThreads);
303         } else {
304           energy = ForceFieldEnergy.energyFactory(assembly);
305         }
306         assembly.setPotential(energy);
307         assemblies.add(assembly);
308         propertyList.add(properties);
309 
310         if (filter instanceof PDBFilter pdbFilter) {
311           List<Character> altLocs = pdbFilter.getAltLocs();
312           if (altLocs.size() > 1 || altLocs.get(0) != ' ') {
313             StringBuilder altLocString = new StringBuilder("\n Alternate locations found [ ");
314             for (Character c : altLocs) {
315               // Do not report the root conformer.
316               if (c == ' ') {
317                 continue;
318               }
319               altLocString.append(format("(%s) ", c));
320             }
321             altLocString.append("]\n");
322             logger.info(altLocString.toString());
323           }
324 
325           /*
326            Alternate conformers may have different chemistry, so
327            they each need to be their own MolecularAssembly.
328           */
329           for (Character c : altLocs) {
330             if (c.equals(' ') || c.equals('A')) {
331               continue;
332             }
333             MolecularAssembly newAssembly = new MolecularAssembly(pathI.toString());
334             newAssembly.setFile(fileI);
335             newAssembly.setForceField(assembly.getForceField());
336             pdbFilter.setAltID(newAssembly, c);
337             pdbFilter.clearSegIDs();
338             if (pdbFilter.readFile()) {
339               String fileName = assembly.getFile().getAbsolutePath();
340               newAssembly.setName(FilenameUtils.getBaseName(fileName) + " " + c);
341               filter.applyAtomProperties();
342               newAssembly.finalize(true, assembly.getForceField());
343               if (nThreads > 0) {
344                 energy = ForceFieldEnergy.energyFactory(assembly, nThreads);
345               } else {
346                 energy = ForceFieldEnergy.energyFactory(assembly);
347               }
348               newAssembly.setPotential(energy);
349               assemblies.add(newAssembly);
350             }
351           }
352         }
353       } else {
354         logger.warning(String.format(" Failed to read file %s", fileI));
355       }
356     }
357     activeAssembly = assemblies.get(0);
358     activeProperties = propertyList.get(0);
359   }
360 
361   /**
362    * setMutations.
363    *
364    * @param mutations a {@link java.util.List} object.
365    */
366   void setMutations(List<Mutation> mutations) {
367     mutationsToApply = mutations;
368   }
369 
370   /**
371    * Setter for the field <code>nThreads</code>.
372    *
373    * @param nThreads Set the number of threads to use for energy calculations.
374    */
375   void setNThreads(int nThreads) {
376     this.nThreads = nThreads;
377   }
378 }