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.cli;
39  
40  import ffx.numerics.Potential;
41  import ffx.potential.MolecularAssembly;
42  import ffx.potential.utils.PotentialsFunctions;
43  import ffx.potential.utils.PotentialsUtils;
44  import ffx.utilities.FFXBinding;
45  import ffx.utilities.FFXCommand;
46  import ffx.utilities.FilePathInfo;
47  import org.apache.log4j.PropertyConfigurator;
48  
49  import javax.annotation.Nullable;
50  import java.io.File;
51  import java.util.ArrayList;
52  import java.util.Arrays;
53  import java.util.List;
54  import java.util.Objects;
55  import java.util.Properties;
56  import java.util.stream.Collectors;
57  
58  import static org.apache.commons.io.FilenameUtils.getExtension;
59  import static org.apache.commons.io.FilenameUtils.getFullPath;
60  import static org.apache.commons.io.FilenameUtils.getName;
61  import static org.apache.commons.io.FilenameUtils.removeExtension;
62  
63  /**
64   * Base class for scripts in the Potentials package, providing some key functions.
65   *
66   * @author Michael J. Schnieders
67   * @since 1.0
68   */
69  public abstract class PotentialCommand extends FFXCommand {
70  
71    /**
72     * An instance of PotentialFunctions passed into the current context.
73     */
74    public PotentialsFunctions potentialFunctions;
75  
76    /**
77     * An active MolecularAssembly passed into the current context or loaded by the Script from a file
78     * argument.
79     */
80    public MolecularAssembly activeAssembly;
81  
82    /**
83     * A temporary directory that contains script artifacts. Temporary files are often created by unit
84     * tests and then deleted.
85     */
86    public File baseDir = null;
87  
88    /**
89     * Default constructor.
90     */
91    public PotentialCommand() {
92      super();
93    }
94  
95    /**
96     * Create a Script using the supplied Binding.
97     *
98     * @param binding Binding with variables to use.
99     */
100   public PotentialCommand(FFXBinding binding) {
101     super(binding);
102   }
103 
104   /**
105    * Create a Script using the supplied command line arguments.
106    *
107    * @param args The command line arguments.
108    */
109   public PotentialCommand(String[] args) {
110     super(args);
111   }
112 
113   /**
114    * Set the Active Assembly. This is a work-around for a strange Groovy static compilation bug where
115    * direct assignment of activeAssembly in Groovy scripts that extend PotentialScript fails (a NPE
116    * result).
117    *
118    * @param molecularAssembly The MolecularAssembly that should be active.
119    */
120   public void setActiveAssembly(MolecularAssembly molecularAssembly) {
121     activeAssembly = molecularAssembly;
122   }
123 
124   /**
125    * Returns a List of all Potential objects associated with this command. Should be written to
126    * tolerate nulls, as many tests run help() and exit without instantiating their Potentials.
127    *
128    * @return All Potentials. Sometimes empty, never null.
129    */
130   public List<Potential> getPotentials() {
131     List<Potential> potentialList = new ArrayList<>();
132     if (activeAssembly != null && activeAssembly.getPotentialEnergy() != null) {
133       potentialList.add(activeAssembly.getPotentialEnergy());
134     }
135     return potentialList;
136   }
137 
138   /**
139    * Reclaims resources associated with all Potential objects associated with this script.
140    *
141    * @return If all Potentials had resources reclaimed.
142    */
143   public boolean destroyPotentials() {
144     boolean allSucceeded = true;
145     for (Potential potential : getPotentials()) {
146       if (potential != null) {
147         allSucceeded = allSucceeded && potential.destroy();
148       }
149     }
150     return allSucceeded;
151   }
152 
153   /**
154    * Returns a List of all Potential objects from the supplied MolecularAssembly array.
155    * Should be written to tolerate nulls, as many tests run help() and exit without
156    * instantiating their Potentials.
157    *
158    * @param assemblies An array of MolecularAssembly instances.
159    * @return All Potentials. Sometimes empty, never null.
160    */
161   public List<Potential> getPotentialsFromAssemblies(MolecularAssembly[] assemblies) {
162     if (assemblies == null) {
163       return new ArrayList<>();
164     }
165     return Arrays.stream(assemblies)
166         .filter(Objects::nonNull)
167         .map(MolecularAssembly::getPotentialEnergy)
168         .filter(Objects::nonNull)
169         .collect(Collectors.toList());
170   }
171 
172   /**
173    * {@inheritDoc}
174    *
175    * <p>Execute the BaseScript init method, then load potential functions.
176    */
177   @Override
178   public boolean init() {
179     if (!super.init()) {
180       return false;
181     }
182 
183     potentialFunctions = (PotentialsFunctions) binding.getVariable("functions");
184 
185     if (potentialFunctions == null) {
186       // Potential package is running.
187       potentialFunctions = new PotentialsUtils();
188       binding.setVariable("functions", potentialFunctions);
189       // Turn off log4j.
190       System.setProperty("log4j.threshold", "OFF");
191       System.setProperty("log4j.rootLogger", "OFF");
192       System.setProperty("log4j1.compatibility", "true");
193       Properties properties = new Properties();
194       properties.setProperty("log4j.threshold", "OFF");
195       properties.setProperty("log4j2.level", "OFF");
196       properties.setProperty("org.apache.logging.log4j.level", "OFF");
197       PropertyConfigurator.configure(properties);
198     }
199 
200     try {
201       activeAssembly = (MolecularAssembly) binding.getVariable("active");
202     } catch (Exception e) {
203       activeAssembly = null;
204     }
205 
206     try {
207       // A temporary directory for script artifacts.
208       baseDir = (File) binding.getVariable("baseDir");
209     } catch (Exception e) {
210       // Ignore.
211     }
212 
213     return true;
214   }
215 
216   /**
217    * If a filename is supplied, open it and return the MolecularAssembly. Otherwise, the current
218    * activeAssembly is returned (which may be null).
219    *
220    * @param filename Filename to open.
221    * @return The active assembly.
222    */
223   public MolecularAssembly getActiveAssembly(@Nullable String filename) {
224     if (filename != null) {
225       // Open the supplied file.
226       MolecularAssembly[] assemblies = {potentialFunctions.open(filename)};
227       activeAssembly = assemblies[0];
228     }
229     return activeAssembly;
230   }
231 
232   /**
233    * If filenames is supplied, open the first entry and return the MolecularAssembly.
234    * Otherwise, the current activeAssembly is returned (which may be null).
235    *
236    * @param filenames Filenames to open.
237    * @return The active assembly.
238    */
239   public MolecularAssembly getActiveAssembly(@Nullable List<String> filenames) {
240     if (filenames != null && !filenames.isEmpty()) {
241       getActiveAssembly(filenames.getFirst());
242     }
243     return activeAssembly;
244   }
245 
246   /**
247    * If a filename is supplied, open it and return the MolecularAssemblies.
248    * Otherwise, the current activeAssembly is returned (which may be null).
249    *
250    * @param filename Filename to open.
251    * @return The active assemblies.
252    */
253   public MolecularAssembly[] getActiveAssemblies(@Nullable String filename) {
254     MolecularAssembly[] assemblies;
255     if (filename != null) {
256       // Open the supplied file.
257       assemblies = potentialFunctions.openAll(filename);
258       activeAssembly = assemblies[0];
259       return assemblies;
260     } else {
261       assemblies = new MolecularAssembly[]{activeAssembly};
262     }
263     return assemblies;
264   }
265 
266 
267   /**
268    * Check that we can write into the current base directory. If not, update the baseDir based on the
269    * supplied filename, including updating the script Binding instance.
270    *
271    * @param dirFromFilename Set the base directory variable <code>baseDir</code> using
272    *                        this filename if it's not set to a writeable directory.
273    * @return Return the base directory as a String (including an appended
274    * <code>File.separator</code>).
275    */
276   public String getBaseDirString(String dirFromFilename) {
277     if (baseDir == null || !baseDir.exists() || !baseDir.isDirectory() || !baseDir.canWrite()) {
278       File file = new File(dirFromFilename);
279       baseDir = new File(getFullPath(file.getAbsolutePath()));
280       binding.setVariable("baseDir", baseDir);
281     }
282     return baseDir.toString() + File.separator;
283   }
284 
285   /**
286    * Parse a filepath into directory, base name, and extension.
287    *
288    * @param filepath The filepath to parse.
289    * @return A FilePathInfo object containing the directory, base name, and extension.
290    */
291   public FilePathInfo parseFilePath(String filepath) {
292     String dirString = getBaseDirString(filepath);
293     String name = getName(filepath);
294     String ext = getExtension(name);
295     String baseName = removeExtension(name);
296     return new FilePathInfo(dirString, baseName, ext);
297   }
298 
299   /**
300    * Create an output File with a new extension based on the input filepath.
301    *
302    * @param filepath     The input filepath.
303    * @param newExtension The new extension for the output file.
304    * @return A File object with the new extension.
305    */
306   public File createOutputFile(String filepath, String newExtension) {
307     FilePathInfo info = parseFilePath(filepath);
308     return new File(info.directory() + info.baseName() + "." + newExtension);
309   }
310 
311   /**
312    * Save MolecularAssemblies to a file based on the supplied extension.
313    *
314    * @param assemblies The MolecularAssembly array to save.
315    * @param filename   The filename.
316    * @param extension  The extension to determine the file format.
317    */
318   public void saveByExtension(MolecularAssembly[] assemblies, String filename, String extension) {
319     if (extension.toUpperCase().contains("XYZ")) {
320       File outputFile = createOutputFile(filename, "xyz");
321       potentialFunctions.saveAsXYZ(assemblies[0], outputFile);
322     } else {
323       File outputFile = createOutputFile(filename, "pdb");
324       potentialFunctions.saveAsPDB(assemblies, outputFile);
325     }
326   }
327 
328   /**
329    * Save MolecularAssembly to a file based on the supplied extension.
330    *
331    * @param molecularAssembly The MolecularAssembly to save.
332    * @param filename          The filename.
333    * @param extension         The extension to determine the file format.
334    */
335   public void saveByExtension(MolecularAssembly molecularAssembly, String filename, String extension) {
336     saveByExtension(new MolecularAssembly[]{molecularAssembly}, filename, extension);
337   }
338 
339   /**
340    * Save MolecularAssemblies to file using the extension of an original filename.
341    *
342    * @param assemblies The MolecularAssembly array to save.
343    * @param filename   The original filename to extract the extension from.
344    */
345   public void saveByOriginalExtension(MolecularAssembly[] assemblies, String filename) {
346     String ext = getExtension(filename);
347     saveByExtension(assemblies, filename, ext);
348   }
349 
350   /**
351    * Save MolecularAssembly to a file using the extension of an original filename.
352    *
353    * @param molecularAssembly The MolecularAssembly array to save.
354    * @param filename          The original filename to extract the extension from.
355    */
356   public void saveByOriginalExtension(MolecularAssembly molecularAssembly, String filename) {
357     String ext = getExtension(filename);
358     saveByExtension(new MolecularAssembly[]{molecularAssembly}, filename, ext);
359   }
360 
361 }