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 }