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-2024.
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.commands;
39  
40  import ffx.algorithms.cli.AlgorithmsCommand;
41  import ffx.numerics.Potential;
42  import ffx.potential.Utilities;
43  import ffx.potential.parsers.BARFilter;
44  import ffx.utilities.FFXCommand;
45  import ffx.utilities.FFXBinding;
46  import picocli.CommandLine.Command;
47  import picocli.CommandLine.Option;
48  import picocli.CommandLine.Parameters;
49  
50  import java.io.BufferedReader;
51  import java.io.File;
52  import java.io.FileReader;
53  import java.io.IOException;
54  import java.util.ArrayList;
55  import java.util.Collections;
56  import java.util.List;
57  import java.util.regex.Matcher;
58  import java.util.regex.Pattern;
59  
60  import static ffx.utilities.FileUtils.traverseFiles;
61  import static java.lang.String.format;
62  import static org.apache.commons.io.FilenameUtils.getBaseName;
63  import static org.apache.commons.io.FilenameUtils.normalize;
64  
65  /**
66   * Performs analysis of Non-equilibrium work simulations. Takes directory paths to the forward and reverse work logs and
67   * uses them to create BAR files and calculate the BAR free energy difference.
68   * <br>
69   * Usage:
70   * <br>
71   * ffxc BAR [options] &lt;structures1&gt; &lt;structures2&gt;
72   */
73  @Command(description = "Performs analysis of Non-equilibrium work simulations.", name = "AnalyzeNEQ")
74  public class AnalyzeNEQ extends AlgorithmsCommand {
75  
76    /**
77     * --reFile --fileSelectionRegex Locate files that match a Regular expression (.* includes all files).
78     */
79    @Option(names = {"--reFile", "--fileSelectionRegex"}, paramLabel = "work.log", defaultValue = "work.log",
80        description = "Locate files that match a regular expression.")
81    private String reFile;
82  
83    /**
84     * --reSearch --fileSearchRegex Search files for a Regular expression.
85     */
86    @Option(names = {"--reSearch", "--fileSearchRegex"}, paramLabel = "Boole", defaultValue = "Boole",
87        description = "Locate this regular expression in log files.")
88    private String reSearch;
89  
90    /**
91     * --bi --barIterations Maximum iterations for BAR calculation.
92     */
93    @Option(names = {"--bi", "--barIterations"}, paramLabel = "100", defaultValue = "100",
94        description = "Maximum iterations for BAR calculation.")
95    private int barIterations;
96  
97    /**
98     * The final argument(s) should be filenames for lambda windows in order.
99     */
100   @Parameters(arity = "2", paramLabel = "path",
101       description = "Two paths to directories. The first to the forward work directory and second to the reverse work directory.")
102   private List<String> directories = null;
103 
104   /**
105    * AnalyzeNEQ Constructor.
106    */
107   public AnalyzeNEQ() {
108     super();
109   }
110 
111   /**
112    * AnalyzeNEQ Constructor.
113    *
114    * @param binding The Binding to use.
115    */
116   public AnalyzeNEQ(FFXBinding binding) {
117     super(binding);
118   }
119 
120   /**
121    * AnalyzeNEQ constructor that sets the command line arguments.
122    *
123    * @param args Command line arguments.
124    */
125   public AnalyzeNEQ(String[] args) {
126     super(args);
127   }
128 
129   /**
130    * {@inheritDoc}
131    */
132   @Override
133   public AnalyzeNEQ run() {
134     // Begin boilerplate code.
135     if (!init()) {
136       return this;
137     }
138 
139     int recurse = 1; // todo have as input option
140 
141     String forwardDir = directories.get(0);
142     String reverseDir = directories.get(1);
143 
144     // Collect the forward works.
145     File fdir = new File(forwardDir);
146     List<File> ffiles = traverseFiles(fdir, recurse, reFile);
147     double[] fworks = grabWorks(ffiles);
148 
149     // Collect the reverse works.
150     File rdir = new File(reverseDir);
151     List<File> rfiles = traverseFiles(rdir, recurse, reFile);
152     double[] rworks = grabWorks(rfiles);
153 
154     // Create BAR file
155     String outputName = getBaseName(fdir.getName()) + "-" + getBaseName(rdir.getName()) + ".bar"; // todo could be specified
156 
157     File barFile = new File(".", "this.xyz");
158     double temp = 300.0; // todo could be specified
159     BARFilter barFilter = new BARFilter(barFile, new double[fworks.length], fworks, rworks, new double[rworks.length],
160         new double[3], new double[3], temp, temp);
161     barFilter.writeFile(outputName, false, false);
162 
163     // Create a Binding for command line arguments.
164     List<String> commandArgs = new ArrayList<>();
165 
166     // Options
167     commandArgs.add("--ns");
168     commandArgs.add("2");
169     commandArgs.add("--ni");
170     commandArgs.add(Integer.toString(barIterations));
171     commandArgs.add("--useTinker");
172     commandArgs.add("--bf");
173     commandArgs.add(outputName);
174 
175     // Parameters
176     // commandArgs.add("test.pdb"); // todo fix - need coord file
177 
178     FFXBinding binding = new FFXBinding();
179     binding.setVariable("args", commandArgs);
180 
181     Class<? extends FFXCommand> script;
182     script = getCommand("BAR");
183 
184     // Create a new instance of the script and run it.
185     try {
186       FFXCommand command = script.getDeclaredConstructor().newInstance();
187       command.setBinding(binding);
188       command.run();
189     } catch (Exception e) {
190       logger.info(" Exception running BAR.");
191       logger.info(e.toString());
192       logger.info(Utilities.stackTraceToString(e));
193     }
194 
195     return this;
196   }
197 
198   /**
199    * Search the files for the regular expression and separate matching lines to get work values.
200    *
201    * @param files - list of found files
202    * @return works - double array with work values from files
203    */
204   private double[] grabWorks(List<File> files) {
205     // Sort the files.
206     Collections.sort(files);
207 
208     int numFiles = files.size();
209     List<Double> works = new ArrayList<>();
210 
211     // Compile the regular expression pattern.
212     Pattern workRegEx = Pattern.compile(reSearch);
213 
214     for (int i = 0; i < numFiles; i++) {
215       File file = files.get(i);
216       if (!file.exists()) {
217         logger.info(format(" Ignoring file that does not exist: %s", file.getAbsolutePath()));
218         continue;
219       }
220 
221       String path = normalize(file.getAbsolutePath());
222       try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
223         String line;
224         // todo above - have ability to request single files with all works or multiple files and grab last work
225         while ((line = reader.readLine()) != null) {
226           Matcher matcher = workRegEx.matcher(line);
227           if (matcher.find()) {
228             String[] workSplit = line.trim().split("\\s+");
229             if (!(workSplit.length == 4 || workSplit.length == 5)) {
230               logger.warning(format("%s line is NOT length four or five: \"%s\"", reSearch, line));
231               continue;
232             }
233             works.add(Double.parseDouble(workSplit[workSplit.length - 1]));
234           }
235         }
236 
237       } catch (IOException e) {
238         System.err.println("Error reading file: " + e.getMessage());
239       }
240     }
241 
242     // Convert List<Double> to double[]
243     double[] result = new double[works.size()];
244     for (int i = 0; i < works.size(); i++) {
245       result[i] = works.get(i);
246     }
247     return result;
248   }
249 
250   /**
251    * {@inheritDoc}
252    */
253   @Override
254   public List<Potential> getPotentials() {
255     return Collections.emptyList();
256   }
257 }