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