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