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 ffx.potential.parsers.PDBFileFilter.isPDB;
41  import static ffx.potential.parsers.XYZFileFilter.isXYZ;
42  import static java.lang.String.format;
43  
44  import ffx.crystal.Crystal;
45  import ffx.potential.ForceFieldEnergy;
46  import ffx.potential.MolecularAssembly;
47  import ffx.potential.parameters.ForceField;
48  import ffx.potential.parsers.PDBFilter;
49  import ffx.potential.parsers.PDBFilter.Mutation;
50  import ffx.potential.parsers.SystemFilter;
51  import ffx.potential.parsers.XYZFilter;
52  import java.io.File;
53  import java.util.Arrays;
54  import java.util.List;
55  import java.util.logging.Level;
56  import java.util.logging.Logger;
57  import org.apache.commons.configuration2.CompositeConfiguration;
58  
59  /**
60   * PotentialsUtils implements core functionality for many Force Field X algorithms and scripts, such
61   * as opening and closing structure files, basic force field evaluations, etc. This implementation
62   * does not do anything on top of what is specified by the interface, and is used primarily by tests.
63   * It is also potentially useful for third parties who would like to use FFX without its graphical
64   * user interface.
65   *
66   * @author Jacob M. Litman
67   * @author Michael J. Schnieders
68   * @since 1.0
69   */
70  public class PotentialsUtils implements PotentialsFunctions {
71  
72    private static final Logger logger = Logger.getLogger(PotentialsUtils.class.getName());
73    private static final Logger potLog = Logger.getLogger("");
74    private static Level levelBak = null;
75    private final long initTime;
76    private long interTime;
77    private SystemFilter lastFilter;
78  
79    /** Constructor for PotentialsUtils. */
80    public PotentialsUtils() {
81      initTime = System.nanoTime();
82      interTime = initTime;
83    }
84  
85    /**
86     * {@inheritDoc}
87     *
88     * <p>Shuts down parallel teams in the force field of the provided MolecularAssembly. Kaminsky's
89     * ParallelTeamThreads' run() methods are infinite loops, and because running threads are always GC
90     * roots, it is necessary to send them a signal to shut down to enable garbage collection.
91     */
92    @Override
93    public void close(MolecularAssembly assembly) {
94      assembly.destroy();
95    }
96  
97    /**
98     * {@inheritDoc}
99     *
100    * <p>Shuts down parallel teams in the force fields of the provided MolecularAssemblys.
101    */
102   @Override
103   public void closeAll(MolecularAssembly[] assemblies) {
104     for (MolecularAssembly assembly : assemblies) {
105       assembly.destroy();
106     }
107   }
108 
109   /**
110    * {@inheritDoc}
111    *
112    * <p>Evaluates the energy of a MolecularAssembly and returns its ForceFieldEnergy object.
113    */
114   @Override
115   public ForceFieldEnergy energy(MolecularAssembly molecularAssembly) {
116     if (molecularAssembly == null) {
117       return null;
118     } else {
119       ForceFieldEnergy energy = molecularAssembly.getPotentialEnergy();
120       if (energy == null) {
121         energy = ForceFieldEnergy.energyFactory(molecularAssembly);
122         molecularAssembly.setPotential(energy);
123       }
124       energy.energy(false, true);
125       return energy;
126     }
127   }
128 
129   /**
130    * {@inheritDoc}
131    */
132   @Override
133   public ForceFieldEnergy[] energy(MolecularAssembly[] molecularAssemblies) {
134     if (molecularAssemblies == null) {
135       return null;
136     }
137     int n = molecularAssemblies.length;
138     ForceFieldEnergy[] forceFieldEnergies = new ForceFieldEnergy[n];
139     if (n == 1) {
140       forceFieldEnergies[0] = energy(molecularAssemblies[0]);
141     } else {
142       for (int i = 0; i < n; i++) {
143         logger.info(format("\n Conformer %d", i + 1));
144         forceFieldEnergies[i] = energy(molecularAssemblies[i]);
145       }
146     }
147     return forceFieldEnergies;
148   }
149 
150   /** {@inheritDoc} */
151   @Override
152   public SystemFilter getFilter() {
153     return lastFilter;
154   }
155 
156   /**
157    * {@inheritDoc}
158    *
159    * <p>Returns true (this is the local implementation).
160    */
161   @Override
162   public boolean isLocal() {
163     return true;
164   }
165 
166   /** {@inheritDoc} */
167   @Override
168   public MolecularAssembly open(String filename) {
169     PotentialsFileOpener opener = new PotentialsFileOpener(filename);
170     opener.run();
171     lastFilter = opener.getFilter();
172     if (opener.getAllAssemblies().length > 1) {
173       logger.log(Level.WARNING, "Found multiple assemblies in file {0}, opening first.", filename);
174     }
175     return opener.getAssembly();
176   }
177 
178   /**
179    * Return one MolecularAssembly.
180    *
181    * @param file a {@link java.io.File} object.
182    * @return a {@link ffx.potential.MolecularAssembly} object.
183    */
184   public MolecularAssembly open(File file) {
185     PotentialsFileOpener opener = new PotentialsFileOpener(file);
186     opener.run();
187     lastFilter = opener.getFilter();
188     if (opener.getAllAssemblies().length > 1) {
189       logger.log(Level.WARNING, "Found multiple assemblies in file {0}, opening first.", file.getName());
190     }
191     return opener.getAssembly();
192   }
193 
194   /**
195    * {@inheritDoc}
196    *
197    * <p>Opens an array of files and returns all created MolecularAssembly objects, setting any
198    * underlying Potential to use a certain number of threads.
199    */
200   @Override
201   public MolecularAssembly[] open(String[] files, int nThreads) {
202     PotentialsFileOpener opener = new PotentialsFileOpener(files);
203     opener.setNThreads(nThreads);
204     opener.run();
205     lastFilter = opener.getFilter();
206     return opener.getAllAssemblies();
207   }
208 
209   /**
210    * {@inheritDoc}
211    *
212    * <p>Opens a file and returns all created MolecularAssembly objects.
213    */
214   @Override
215   public MolecularAssembly[] openAll(String file) {
216     PotentialsFileOpener opener = new PotentialsFileOpener(file);
217     opener.run();
218     lastFilter = opener.getFilter();
219     return opener.getAllAssemblies();
220   }
221 
222   /**
223    * {@inheritDoc}
224    *
225    * <p>Opens an array of files and returns the created MolecularAssembly objects.
226    */
227   @Override
228   public MolecularAssembly[] openAll(String[] files) {
229     PotentialsFileOpener opener = new PotentialsFileOpener(files);
230     opener.run();
231     lastFilter = opener.getFilter();
232     logger.info(" Filter type: " + lastFilter.toString());
233     return opener.getAllAssemblies();
234   }
235 
236   /**
237    * {@inheritDoc}
238    *
239    * <p>Opens a file and returns all created MolecularAssembly objects, setting any underlying
240    * Potential to use a certain number of threads.
241    */
242   @Override
243   public MolecularAssembly[] openAll(String file, int nThreads) {
244     PotentialsFileOpener opener = new PotentialsFileOpener(file);
245     opener.setNThreads(nThreads);
246     opener.run();
247     lastFilter = opener.getFilter();
248     logger.info(" Filter type: " + lastFilter.toString());
249     return opener.getAllAssemblies();
250   }
251 
252   /**
253    * Open one filename string without printing all the header material.
254    *
255    * @param filename a {@link java.lang.String} object.
256    * @return a {@link ffx.potential.MolecularAssembly} object.
257    */
258   public MolecularAssembly openQuietly(String filename) {
259     setSilentPotential(true);
260     MolecularAssembly molecularAssembly = open(filename);
261     setSilentPotential(false);
262     return molecularAssembly;
263   }
264 
265   /**
266    * Mutates file on-the-fly as it is being opened. Used to open files for pHMD in fully-protonated
267    * form.
268    *
269    * @param file a {@link java.io.File} object.
270    * @param mutations a {@link java.util.List} object.
271    * @return a {@link ffx.potential.MolecularAssembly} object.
272    */
273   public MolecularAssembly openWithMutations(File file, List<Mutation> mutations) {
274     PotentialsFileOpener opener = new PotentialsFileOpener(file);
275     opener.setMutations(mutations);
276     opener.run();
277     lastFilter = opener.getFilter();
278     if (opener.getAllAssemblies().length > 1) {
279       logger.log(
280           Level.WARNING, "Found multiple assemblies in file {0}, opening first.", file.getName());
281     }
282     return opener.getAssembly();
283   }
284 
285   /**
286    * {@inheritDoc}
287    *
288    * <p>Returns the energy of a MolecularAssembly in kcal/mol (as a double) and prints the energy
289    * evaluation
290    */
291   @Override
292   public double returnEnergy(MolecularAssembly assembly) {
293     if (assembly == null) {
294       logger.info(" Molecular assembly was null - skipping energy");
295       return 0.0;
296     } else {
297       ForceFieldEnergy energy = assembly.getPotentialEnergy();
298       if (energy == null) {
299         energy = ForceFieldEnergy.energyFactory(assembly);
300         assembly.setPotential(energy);
301       }
302       return energy.energy(false, true);
303     }
304   }
305 
306   /**
307    * {@inheritDoc}
308    *
309    * <p>Saves the current state of a MolecularAssembly to an XYZ file.
310    */
311   @Override
312   public void save(MolecularAssembly assembly, File file) {
313     if (isXYZ(file)) {
314       saveAsXYZ(assembly, file);
315     } else if (isPDB(file)) {
316       saveAsPDB(assembly, file);
317     }
318   }
319 
320   /**
321    * {@inheritDoc}
322    *
323    * <p>Saves the current state of a MolecularAssembly to an XYZ file as a P1 crystal.
324    */
325   @Override
326   public void saveAsXYZinP1(MolecularAssembly assembly, File file) {
327     if (assembly == null) {
328       logger.info(" Assembly to save was null.");
329     } else if (file == null) {
330       logger.info(" No valid file provided to save assembly to.");
331     } else {
332       XYZFilter filter = new XYZFilter(file, assembly, null, null);
333       ForceField forceField = assembly.getForceField();
334       final double a = forceField.getDouble("A_AXIS", 10.0);
335       final double b = forceField.getDouble("B_AXIS", a);
336       final double c = forceField.getDouble("C_AXIS", a);
337       final double alpha = forceField.getDouble("ALPHA", 90.0);
338       final double beta = forceField.getDouble("BETA", 90.0);
339       final double gamma = forceField.getDouble("GAMMA", 90.0);
340       final String spacegroup = forceField.getString("SPACEGROUP", "P1");
341       Crystal crystal = new Crystal(a, b, c, alpha, beta, gamma, spacegroup);
342       if (!filter.writeFileAsP1(file, false, crystal)) {
343         logger.info(format(" Save failed for %s", assembly));
344       }
345       lastFilter = filter;
346     }
347   }
348 
349   /**
350    * {@inheritDoc}
351    * <p>Saves the current state of a MolecularAssembly to an XYZ file as a replicated crystal.
352    */
353   @Override
354   public void saveAsXYZasReplicates(MolecularAssembly assembly, File file, int[] lmn) {
355     if (assembly == null) {
356       logger.info(" Assembly to save was null.");
357     } else if (file == null) {
358       logger.info(" No valid file provided to save assembly to.");
359     } else {
360       XYZFilter filter = new XYZFilter(file, assembly, null, null);
361       ForceField forceField = assembly.getForceField();
362       final double a = forceField.getDouble("A_AXIS", 10.0);
363       final double b = forceField.getDouble("B_AXIS", a);
364       final double c = forceField.getDouble("C_AXIS", a);
365       final double alpha = forceField.getDouble("ALPHA", 90.0);
366       final double beta = forceField.getDouble("BETA", 90.0);
367       final double gamma = forceField.getDouble("GAMMA", 90.0);
368       final String spacegroup = forceField.getString("SPACEGROUP", "P1");
369       Crystal crystal = new Crystal(a, b, c, alpha, beta, gamma, spacegroup);
370       if (!filter.writeFileAsP1(file, false, crystal, lmn, null)) {
371         logger.info(format(" Save failed for %s", assembly));
372       }
373       lastFilter = filter;
374     }
375   }
376 
377   /**
378    * {@inheritDoc}
379    *
380    * <p>Saves the current state of a MolecularAssembly to a PDB file.
381    */
382   @Override
383   public void saveAsPDB(MolecularAssembly assembly, File file) {
384     saveAsPDB(assembly, file, true, false);
385   }
386 
387   /**
388    * {@inheritDoc}
389    *
390    * <p>Saves the current state of a MolecularAssembly to a PDB file.
391    */
392   @Override
393   public void saveAsPDB(MolecularAssembly assembly, File file, boolean writeEnd, boolean append) {
394     if (assembly == null) {
395       logger.info(" Assembly to save was null.");
396     } else if (file == null) {
397       logger.info(" No valid file provided to save assembly to.");
398     } else {
399       PDBFilter pdbFilter = new PDBFilter(file, assembly, null, null);
400       if (!pdbFilter.writeFile(file, append, false, writeEnd)) {
401         logger.info(format(" Save failed for %s", assembly));
402       }
403       lastFilter = pdbFilter;
404     }
405   }
406 
407   /**
408    * {@inheritDoc}
409    *
410    * <p>Saves the current state of an array of MolecularAssemblys to a PDB file.
411    */
412   @Override
413   public void saveAsPDB(MolecularAssembly[] assemblies, File file) {
414     if (assemblies == null) {
415       logger.info(" Null array of molecular assemblies to write.");
416     } else if (assemblies.length == 0) {
417       logger.info(" Zero-length array of molecular assemblies to write.");
418     } else if (file == null) {
419       logger.info(" No valid file to write to.");
420     } else {
421       ForceField forceField = assemblies[0].getForceField();
422       CompositeConfiguration properties = forceField.getProperties();
423       PDBFilter pdbFilter = new PDBFilter(file, Arrays.asList(assemblies), forceField, properties);
424       pdbFilter.writeFile(file, false);
425       lastFilter = pdbFilter;
426     }
427   }
428 
429   /**
430    * {@inheritDoc}
431    *
432    * <p>Saves the current state of a MolecularAssembly to an XYZ file.
433    */
434   @Override
435   public void saveAsXYZ(MolecularAssembly assembly, File file) {
436     if (assembly == null) {
437       logger.info(" Assembly to save was null.");
438     } else if (file == null) {
439       logger.info(" No valid file provided to save assembly to.");
440     } else {
441       ForceField forceField = assembly.getForceField();
442       CompositeConfiguration properties = forceField.getProperties();
443       XYZFilter xyzFilter = new XYZFilter(file, assembly, forceField, properties);
444       if (!xyzFilter.writeFile(file, false)) {
445         logger.info(format(" Save failed for %s", assembly));
446       }
447       lastFilter = xyzFilter;
448     }
449   }
450 
451   /** {@inheritDoc} */
452   @Override
453   public void saveAsPDBinP1(MolecularAssembly assembly, File file) {
454     saveAsPDBinP1(assembly, file, null);
455   }
456 
457   public void saveAsPDBinP1(MolecularAssembly assembly, File file, int[] lmn){
458     if (assembly == null) {
459       logger.info(" Assembly to save was null.");
460     } else if (file == null) {
461       logger.info(" No valid file provided to save assembly to.");
462     } else {
463       ForceField forceField = assembly.getForceField();
464       CompositeConfiguration properties = forceField.getProperties();
465       PDBFilter pdbFilter = new PDBFilter(file, assembly, forceField, properties);
466       pdbFilter.setLMN(lmn);
467       lastFilter = pdbFilter;
468       pdbFilter.writeFileAsP1(file);
469     }
470   }
471 
472   /**
473    * {@inheritDoc}
474    *
475    * <p>Logs time since this interface was created and the last time this method was called.
476    */
477   @Override
478   public double time() {
479     long currTime = System.nanoTime();
480     logger.info(format(" Time since interface established: %f", (currTime - initTime) * 1.0E-9));
481     double elapsed = (currTime - interTime) * 1.0E-9;
482     interTime = currTime;
483     logger.info(format(" Time since last timer call: %f", elapsed));
484     return elapsed;
485   }
486 
487   /**
488    * setSilentPotential.
489    *
490    * @param silent a boolean.
491    */
492   void setSilentPotential(boolean silent) {
493     if (silent && potLog.isLoggable(Level.INFO) && levelBak == null) {
494       levelBak = potLog.getLevel();
495       potLog.setLevel(Level.WARNING);
496     }
497     if (!silent && levelBak != null) {
498       potLog.setLevel(levelBak);
499       levelBak = null;
500     }
501   }
502 }