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