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.algorithms.cli;
39  
40  import ffx.algorithms.AlgorithmFunctions;
41  import ffx.algorithms.AlgorithmListener;
42  import ffx.algorithms.AlgorithmUtils;
43  import ffx.crystal.Crystal;
44  import ffx.numerics.Potential;
45  import ffx.potential.MolecularAssembly;
46  import ffx.utilities.FFXCommand;
47  import ffx.utilities.FFXBinding;
48  import org.apache.commons.lang3.Strings;
49  
50  import javax.annotation.Nullable;
51  import java.io.File;
52  import java.util.ArrayList;
53  import java.util.Arrays;
54  import java.util.List;
55  import java.util.Objects;
56  import java.util.stream.Collectors;
57  
58  import static java.lang.String.format;
59  
60  /**
61   * Base class for scripts in the Algorithms package, providing some key functions.
62   *
63   * @author Michael J. Schnieders
64   * @since 1.0
65   */
66  public class AlgorithmsCommand extends FFXCommand {
67  
68    /**
69     * An instance of AlgorithmFunctions passed into the current context.
70     */
71    public AlgorithmFunctions algorithmFunctions;
72  
73    /**
74     * An active MolecularAssembly passed into the current context or loaded by the Script from a file
75     * argument.
76     */
77    public MolecularAssembly activeAssembly;
78  
79    /**
80     * An instance of the AlgorithmListener interface.
81     */
82    public AlgorithmListener algorithmListener;
83  
84    /**
85     * The directory in which to place output files. Mostly for tests.
86     */
87    protected File baseDir;
88  
89    public AlgorithmsCommand() {
90      super();
91    }
92  
93    public AlgorithmsCommand(FFXBinding binding) {
94      super(binding);
95    }
96  
97    /**
98     * Create a Script using the supplied command line arguments.
99     *
100    * @param args The command line arguments.
101    */
102   public AlgorithmsCommand(String[] args) {
103     super(args);
104   }
105 
106   /**
107    * Reclaims resources associated with all Potential objects associated with this script.
108    *
109    * @return If all Potentials had resources reclaimed.
110    */
111   public boolean destroyPotentials() {
112     boolean allSucceeded = true;
113     for (Potential potent : getPotentials()) {
114       logger.fine(format(" Potential %s is being destroyed. ", potent));
115       allSucceeded = allSucceeded && potent.destroy();
116     }
117     return allSucceeded;
118   }
119 
120   /**
121    * Returns a List of all Potential objects associated with this script.
122    *
123    * @return All Potentials. Sometimes empty, never null.
124    */
125   public List<Potential> getPotentials() {
126     List<Potential> potentials = new ArrayList<>();
127     if (activeAssembly != null && activeAssembly.getPotentialEnergy() != null) {
128       potentials.add(activeAssembly.getPotentialEnergy());
129     }
130     return potentials;
131   }
132 
133   /**
134    * Returns a List of all Potential objects from the supplied MolecularAssembly array.
135    * Should be written to tolerate nulls, as many tests run help() and exit without
136    * instantiating their Potentials.
137    *
138    * @param assemblies An array of MolecularAssembly instances.
139    * @return All Potentials. Sometimes empty, never null.
140    */
141   public List<Potential> getPotentialsFromAssemblies(MolecularAssembly[] assemblies) {
142     if (assemblies == null) {
143       return new ArrayList<>();
144     }
145     return Arrays.stream(assemblies)
146         .filter(Objects::nonNull)
147         .map(MolecularAssembly::getPotentialEnergy)
148         .filter(Objects::nonNull)
149         .collect(Collectors.toList());
150   }
151 
152   /**
153    * {@inheritDoc}
154    *
155    * <p>Execute the BaseScript init method, then load algorithm functions.
156    */
157   @Override
158   public boolean init() {
159     if (!super.init()) {
160       return false;
161     }
162 
163     if (binding.hasVariable("functions")) {
164       algorithmFunctions = (AlgorithmFunctions) binding.getVariable("functions");
165     } else {
166       algorithmFunctions = new AlgorithmUtils();
167       binding.setVariable("functions", algorithmFunctions);
168     }
169 
170     activeAssembly = null;
171     if (binding.hasVariable("active")) {
172       activeAssembly = (MolecularAssembly) binding.getVariable("active");
173     }
174 
175     algorithmListener = null;
176     if (binding.hasVariable("listener")) {
177       algorithmListener = (AlgorithmListener) binding.getVariable("listener");
178     }
179 
180     if (binding.hasVariable("baseDir")) {
181       baseDir = (File) binding.getVariable("baseDir");
182     }
183 
184     return true;
185   }
186 
187   /**
188    * Sets the directory this script should save files to. Mostly used for tests.
189    *
190    * @param baseDir Directory to save output to.
191    */
192   public void setBaseDir(File baseDir) {
193     this.baseDir = baseDir;
194   }
195 
196   /**
197    * Return a File in the base directory with the same name as the input file.
198    * <p>
199    * This will just be the original file if baseDir was never set, which is the case for production
200    * runs.
201    *
202    * @param file File to find a save location for.
203    * @return Returns a File in the base directory with the same name as the input file.
204    */
205   protected File saveDirFile(File file) {
206     if (baseDir == null || !baseDir.exists() || !baseDir.isDirectory() || !baseDir.canWrite()) {
207       return file;
208     } else {
209       String baseName = file.getName();
210       String newName = baseDir.getAbsolutePath() + File.separator + baseName;
211       return new File(newName);
212     }
213   }
214 
215   /**
216    * If a filename is supplied, open it and return the MolecularAssembly. Otherwise, the current
217    * activeAssembly is returned (which may be null).
218    *
219    * @param filename Filename to open.
220    * @return The active assembly.
221    */
222   public MolecularAssembly getActiveAssembly(@Nullable String filename) {
223     if (filename != null) {
224       // Open the supplied file.
225       MolecularAssembly[] assemblies = {algorithmFunctions.open(filename)};
226       activeAssembly = assemblies[0];
227     }
228     return activeAssembly;
229   }
230 
231   /**
232    * If a filename is supplied, open it and return the MolecularAssemblies. Otherwise, the current
233    * activeAssembly is returned (which may be null).
234    *
235    * @param filename Filename to open.
236    * @return The active assemblies.
237    */
238   public MolecularAssembly[] getActiveAssemblies(@Nullable String filename) {
239     MolecularAssembly[] assemblies;
240     if (filename != null) {
241       // Open the supplied file.
242       assemblies = algorithmFunctions.openAll(filename);
243       activeAssembly = assemblies[0];
244       return assemblies;
245     } else {
246       assemblies = new MolecularAssembly[]{activeAssembly};
247     }
248     return assemblies;
249   }
250 
251   /**
252    * Update the title line of the structure with Energy and Density.
253    *
254    * @param energy Newly minimized energy value.
255    */
256   public void updateTitle(double energy) {
257     // Replace existing energy and density label if present
258     String oldName = activeAssembly.getName();
259     Crystal crystal = activeAssembly.getCrystal();
260     if (crystal != null && !crystal.aperiodic()) {
261       double density = crystal.getDensity(activeAssembly.getMass());
262       if (Strings.CI.contains(oldName, "Energy:")
263           || Strings.CI.contains(oldName, "Density:")) {
264         String[] tokens = oldName.trim().split(" +");
265         int numTokens = tokens.length;
266         // The first element should always be the number of atoms in XYZ.
267         StringBuilder sb = new StringBuilder();
268         for (int i = 0; i < numTokens; i++) {
269           if (Strings.CI.contains(tokens[i], "Energy:")) {
270             // i++ skips the current entry (value associated with "Energy")
271             tokens[i++] = Double.toString(energy);
272           } else if (Strings.CI.contains(tokens[i], "Density:")) {
273             // i++ skips the current entry (value associated with "Density")
274             tokens[i++] = Double.toString(density);
275           } else {
276             // Accrue previous name.
277             sb.append(tokens[i]).append(" ");
278           }
279         }
280         // Opted to add energy/density after to preserve formatting.
281         activeAssembly.setName(format("%s Energy: %9.4f Density: %9.4f",
282             sb, energy, density));
283       } else {
284         // Append energy and density to structure name (line 1 of XYZ).
285         activeAssembly.setName(format("%s Energy: %9.4f Density: %9.4f",
286             oldName, energy, density));
287       }
288     } else {
289       if (Strings.CI.contains(oldName, "Energy:")) {
290         String[] tokens = oldName.trim().split(" +");
291         int numTokens = tokens.length;
292         // The first element should always be number of atoms in XYZ.
293         StringBuilder sb = new StringBuilder();
294         for (int i = 0; i < numTokens; i++) {
295           if (Strings.CI.contains(tokens[i], "Energy:")) {
296             // i++ skips the current entry (value associated with "Energy")
297             tokens[i++] = Double.toString(energy);
298           } else {
299             // Accrue previous name.
300             sb.append(tokens[i]).append(" ");
301           }
302         }
303         // Opted to add energy/density after to preserve formatting.
304         activeAssembly.setName(format("%s Energy: %9.4f", sb, energy));
305       } else {
306         // Append energy and density to the structure name (line 1 of XYZ).
307         activeAssembly.setName(format("%s Energy: %9.4f", oldName, energy));
308       }
309     }
310   }
311 }