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