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