1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.algorithms.commands;
39
40 import ffx.algorithms.cli.AlgorithmsCommand;
41 import ffx.crystal.CrystalPotential;
42 import ffx.numerics.Potential;
43 import ffx.numerics.estimator.BennettAcceptanceRatio;
44 import ffx.numerics.estimator.EstimateBootstrapper;
45 import ffx.numerics.estimator.MBARFilter;
46 import ffx.numerics.estimator.MultistateBennettAcceptanceRatio;
47 import ffx.numerics.estimator.MultistateBennettAcceptanceRatio.SeedType;
48 import ffx.potential.MolecularAssembly;
49 import ffx.potential.bonded.LambdaInterface;
50 import ffx.potential.cli.AlchemicalOptions;
51 import ffx.potential.cli.TopologyOptions;
52 import ffx.potential.parsers.SystemFilter;
53 import ffx.utilities.FFXBinding;
54 import org.apache.commons.io.FilenameUtils;
55 import picocli.CommandLine.Command;
56 import picocli.CommandLine.Mixin;
57 import picocli.CommandLine.Option;
58 import picocli.CommandLine.Parameters;
59
60 import java.io.File;
61 import java.util.Arrays;
62 import java.util.Collections;
63 import java.util.List;
64
65 import static java.lang.String.format;
66
67
68
69
70
71
72
73
74 @Command(description = " Evaluates a free energy change with the Multistate Bennett Acceptance Ratio algorithm.",
75 name = "MBAR")
76 public class MBAR extends AlgorithmsCommand {
77
78 @Mixin
79 private AlchemicalOptions alchemicalOptions;
80
81 @Mixin
82 private TopologyOptions topologyOptions;
83
84 @Option(names = {"--bar"}, paramLabel = "true",
85 description = "Run BAR calculation as well using a subset of the MBAR data.")
86 private boolean bar = true;
87
88 @Option(names = {"--convergence"}, paramLabel = "false",
89 description = "Run MBAR multiple times across different time periods of the data to examine the change in FE over time.")
90 private boolean convergence = false;
91
92 @Option(names = {"--numBootstrap", "--nb"}, paramLabel = "0",
93 description = "Number of bootstrap samples to use.")
94 private int numBootstrap = 0;
95
96 @Option(names = {"--numLambda", "--nL", "--nw"}, paramLabel = "-1",
97 description = "Required for lambda energy evaluations. Ensure numLambda is consistent with the trajectory lambdas, i.e. gaps between traj can be filled easily. nL >> nTraj is recommended.")
98 private int numLambda = -1;
99
100 @Option(names = {"--lambdaDerivative", "--lD"}, paramLabel = "false",
101 description = "Calculate lambda derivatives for each snapshot.")
102 private boolean lambdaDerivative = false;
103
104 @Option(names = {"--continuousLambda", "--cL"}, paramLabel = "false",
105 description = "Data comes from continuous lambda source and only contains mbar file.")
106 private boolean continuousLambda = false;
107
108 @Option(names = {"--outputDir", "--oD"}, paramLabel = "",
109 description = "Where to place MBAR files. Default is ../mbarFiles/energy_(window#).mbar. Will write out a file called energy_0.mbar.")
110 private String outputDirectory = "";
111
112 @Option(names = {"--seed"}, paramLabel = "BAR",
113 description = "Seed MBAR calculation with this: ZEROS, ZWANZIG, BAR. Fallback to ZEROS if input is does not or is unlikely to converge.")
114 private String seedWith = "BAR";
115
116 @Option(names = {"--tol", "--tolerance"}, paramLabel = "1e-7",
117 description = "Iteration change tolerance.")
118 private double tol = 1e-7;
119
120 @Option(names = {"--ss", "--startSnapshot"}, paramLabel = "-1",
121 description = "Start at this snapshot when reading in tinker BAR files.")
122 private int startingSnapshot = -1;
123
124 @Option(names = {"--es", "--endSnapshot"}, paramLabel = "-1",
125 description = "End at this snapshot when reading in tinker BAR files.")
126 private int endingSnapshot = -1;
127
128 @Option(names = {"--verbose"}, paramLabel = "false",
129 description = "Log weight matrices, iterations, and other details.")
130 private boolean verbose = false;
131
132
133
134
135 @Parameters(arity = "1..*", paramLabel = "files",
136 description = "Path to MBAR/BAR files to analyze or an PDB/XYZ in a directory with archive(s).")
137 private List<String> fileList = null;
138
139 public MultistateBennettAcceptanceRatio mbar = null;
140 private int numTopologies;
141
142
143
144
145 public MBAR() {
146 super();
147 }
148
149
150
151
152
153
154 public MBAR(FFXBinding binding) {
155 super(binding);
156 }
157
158
159
160
161
162
163 public MBAR(String[] args) {
164 super(args);
165 }
166
167
168
169
170 @Override
171 public MBAR run() {
172 if (!init()) {
173 return this;
174 }
175
176
177 if (fileList == null) {
178 logger.severe("No path to MBAR/BAR or trajectory(s) file names specified.");
179 return this;
180 }
181 int nFiles = fileList.size();
182 String[] fileNames = new String[nFiles];
183 File[] files = new File[nFiles];
184 for (int i = 0; i < nFiles; i++) {
185 fileNames[i] = fileList.get(i);
186 files[i] = (new File(fileNames[i])).getAbsoluteFile();
187 if (!files[i].exists()) {
188 logger.severe("File does not exist: " + fileNames[i]);
189 return this;
190 }
191 }
192 boolean isArc = !files[0].isDirectory();
193
194
195 if (isArc) {
196 if (numLambda == -1) {
197 logger.severe("numLambda must be specified for lambda energy evaluations.");
198 return this;
199 }
200
201 File parent = files[0].getParentFile();
202 int window;
203 File outputDir;
204 if (outputDirectory.isEmpty()) {
205 window = Integer.parseInt(parent.getName());
206 outputDir = new File(parent.getParentFile(), "mbarFiles");
207 if (!outputDir.exists()) {
208 outputDir.mkdir();
209 }
210 } else {
211 outputDir = new File(outputDirectory);
212 if (!outputDir.exists()) {
213 outputDir.mkdir();
214 }
215 window = 0;
216 }
217
218
219 File outputFile = new File(outputDir, "energy_" + window + ".mbar");
220
221 double[][][] energiesAndDerivatives = getEnergyForLambdas(files, numLambda);
222 double[][] energies = energiesAndDerivatives[0];
223 MultistateBennettAcceptanceRatio.writeFile(energies, outputFile, 298);
224 if (lambdaDerivative) {
225 double[][] lambdaDerivatives = energiesAndDerivatives[1];
226 File outputDerivFile = new File(outputDir, "derivatives_" + window + ".mbar");
227 MultistateBennettAcceptanceRatio.writeFile(lambdaDerivatives, outputDerivFile, 298);
228 }
229 return this;
230 }
231
232
233 File path = new File(fileList.get(0));
234 if (!path.exists()) {
235 logger.severe("Path to MBAR/BAR fileNames does not exist: " + path);
236 return this;
237 }
238 if (!path.isDirectory() && !(path.isFile() && path.canRead())) {
239 logger.severe("Path to MBAR/BAR fileNames is not accessible: " + path);
240 return this;
241 }
242 MBARFilter filter = new MBARFilter(path, continuousLambda);
243 if (startingSnapshot >= 0) {
244 filter.setStartSnapshot(startingSnapshot);
245 logger.info("Starting with snapshot index: " + startingSnapshot);
246 }
247 if (endingSnapshot >= 0) {
248 filter.setEndSnapshot(endingSnapshot);
249 logger.info("Ending with snapshot index: " + endingSnapshot);
250 }
251 seedWith = seedWith.toUpperCase();
252 SeedType seed = SeedType.valueOf(seedWith);
253 if (seed == null) {
254 logger.severe("Invalid seed type: " + seedWith);
255 return this;
256 }
257 MultistateBennettAcceptanceRatio.VERBOSE = verbose;
258
259
260 mbar = filter.getMBAR(seed, tol);
261 this.mbar = mbar;
262 if (mbar == null) {
263 logger.severe("Could not create MBAR object.");
264 return this;
265 }
266
267
268 logger.info("\n MBAR Results:");
269 double[] dGs = mbar.getFreeEnergyDifferences();
270 double[] uncertainties = mbar.getFEDifferenceUncertainties();
271 logger.info(format(" Total dG = %10.4f +/- %10.4f kcal/mol\n",
272 mbar.getTotalFreeEnergyDifference(), mbar.getTotalFEDifferenceUncertainty()));
273 for (int i = 0; i < dGs.length; i++) {
274 logger.info(format(" dG %3d = %10.4f +/- %10.4f kcal/mol", i, dGs[i], uncertainties[i]));
275 }
276
277 logger.info("\n MBAR Enthalpy & Entropy Results:");
278 double[] enthalpies = mbar.getEnthalpyDifferences();
279 double[] entropies = mbar.getBinEntropies();
280 double totalEnthalpy = sum(enthalpies);
281 double totalEntropy = sum(entropies);
282 logger.info(format(" Total dG = %10.4f (dH) - %10.4f (TdS) kcal/mol\n", totalEnthalpy, totalEntropy));
283 for (int i = 0; i < enthalpies.length; i++) {
284 logger.info(format(" dG %3d = %10.4f (dH) - %10.4f (TdS) kcal/mol", i, enthalpies[i], entropies[i]));
285 }
286
287 logger.info("\n MBAR uncertainty between all i & j: ");
288 double[][] uncertaintyMatrix = mbar.getUncertaintyMatrix();
289 for (int i = 0; i < uncertaintyMatrix.length; i++) {
290 StringBuilder sb = new StringBuilder();
291 sb.append(" [");
292 for (int j = 0; j < uncertaintyMatrix[i].length; j++) {
293 sb.append(format(" %6.5f ", uncertaintyMatrix[i][j]));
294 }
295 sb.append("]");
296 logger.info(sb.toString());
297 }
298
299
300 boolean observableData = filter.readObservableData(true, false, true);
301 if (observableData) {
302 logger.info("\n Observable data read in.");
303 }
304 boolean biasData = filter.readObservableData(true, true, false);
305 if (biasData) {
306 logger.info(" Bias data read in.");
307 }
308 if (observableData) {
309 logger.info("\n MBAR Observable Data: ");
310 double[] observableValues = mbar.getObservationEnsembleAverages();
311 for (int i = 0; i < observableValues.length; i++) {
312 logger.info(format(" %3d = %10.4f ", i, observableValues[i]));
313 }
314 logger.info(" Integral: " + mbar.getTIIntegral());
315 }
316 logger.info("\n");
317
318 if (bar) {
319 try {
320 logger.info("\n BAR Results:");
321 BennettAcceptanceRatio bar = mbar.getBAR();
322 logger.info(format(" Total dG = %10.4f +/- %10.4f kcal/mol\n", bar.getTotalFreeEnergyDifference(),
323 bar.getTotalFEDifferenceUncertainty()));
324 dGs = bar.getFreeEnergyDifferences();
325 uncertainties = bar.getFEDifferenceUncertainties();
326 for (int i = 0; i < dGs.length; i++) {
327 logger.info(format(" dG %3d = %10.4f +/- %10.4f kcal/mol", i, dGs[i], uncertainties[i]));
328 }
329 enthalpies = bar.getEnthalpyDifferences();
330 totalEnthalpy = sum(enthalpies);
331 logger.info(format("\n Total dH = %10.4f kcal/mol\n", totalEnthalpy));
332 for (int i = 0; i < enthalpies.length; i++) {
333 logger.info(format(" dH %3d = %10.4f kcal/mol", i, enthalpies[i]));
334 }
335 } catch (Exception ignored) {
336 logger.warning(" BAR calculation failed to converge.");
337 }
338 }
339 logger.info("\n");
340
341 if (numBootstrap != 0) {
342 EstimateBootstrapper bootstrapper = new EstimateBootstrapper(mbar);
343 bootstrapper.bootstrap(numBootstrap);
344 logger.info("\n MBAR Bootstrap Results from " + numBootstrap + " Samples:");
345 logger.info(format(" Total dG = %10.4f +/- %10.4f kcal/mol", bootstrapper.getTotalFreeEnergyDifference(),
346 bootstrapper.getTotalFEDifferenceUncertainty()));
347 dGs = bootstrapper.getFreeEnergyDifferences();
348 uncertainties = bootstrapper.getFEDifferenceStdDevs();
349 for (int i = 0; i < dGs.length; i++) {
350 logger.info(format(" dG %3d = %10.4f +/- %10.4f kcal/mol", i, dGs[i], uncertainties[i]));
351 }
352 logger.info("\n");
353 if (bar) {
354 try {
355 logger.info("\n BAR Bootstrap Results:");
356 bootstrapper = new EstimateBootstrapper(mbar.getBAR());
357 bootstrapper.bootstrap(numBootstrap);
358 logger.info(format(" Total dG = %10.4f +/- %10.4f kcal/mol", bootstrapper.getTotalFreeEnergyDifference(),
359 bootstrapper.getTotalFEDifferenceUncertainty()));
360 dGs = bootstrapper.getFreeEnergyDifferences();
361 uncertainties = bootstrapper.getFEDifferenceStdDevs();
362 for (int i = 0; i < dGs.length; i++) {
363 logger.info(format(" dG %3d = %10.4f +/- %10.4f kcal/mol", i, dGs[i], uncertainties[i]));
364 }
365 } catch (Exception ignored) {
366 logger.warning(" BAR calculation failed to converge.");
367 }
368 }
369 }
370
371 if (convergence) {
372 MultistateBennettAcceptanceRatio.FORCE_ZEROS_SEED = true;
373 MultistateBennettAcceptanceRatio[] mbarPeriodComparison =
374 filter.getPeriodComparisonMBAR(seed, 1e-7);
375 double[][] dGPeriod = new double[mbarPeriodComparison.length][];
376 for (int i = 0; i < mbarPeriodComparison.length; i++) {
377 dGPeriod[i] = mbarPeriodComparison[i].getFreeEnergyDifferences();
378 }
379 logger.info("\n MBAR Period Comparison Results:");
380 logger.info(format(" %10d%%%10d%%%10d%%%10d%%%10d%%%10d%%%10d%%%10d%%%10d%%%10d%% ",
381 10, 20, 30, 40, 50, 60, 70, 80, 90, 100));
382 double[] totals = new double[dGPeriod[0].length];
383 for (int i = 0; i < dGPeriod[0].length; i++) {
384 StringBuilder sb = new StringBuilder();
385 sb.append(" dG_").append(i).append(": ");
386 for (int j = 0; j < dGPeriod.length; j++) {
387 sb.append(format("%10.4f ", dGPeriod[j][i]));
388 totals[j] += dGPeriod[j][i];
389 }
390 logger.info(sb.toString());
391 }
392 StringBuilder totalsSB = new StringBuilder();
393 for (int i = 0; i < totals.length; i++) {
394 totalsSB.append(format("%10.4f ", totals[i]));
395 }
396 logger.info("");
397 logger.info(" Tot: " + totalsSB.toString());
398 }
399 return this;
400 }
401
402 private static double sum(double[] values) {
403 double sum = 0;
404 for (double value : values) {
405 sum += value;
406 }
407 return sum;
408 }
409
410 private double[][][] getEnergyForLambdas(File[] files, int nLambda) {
411
412
413 numTopologies = files.length;
414 int threadsPerTopology = topologyOptions.getThreadsPerTopology(numTopologies);
415 MolecularAssembly[] molecularAssemblies = new MolecularAssembly[numTopologies];
416 SystemFilter[] openers = new SystemFilter[numTopologies];
417
418
419 alchemicalOptions.setAlchemicalProperties();
420 topologyOptions.setAlchemicalProperties(numTopologies);
421
422 if (numTopologies == 2) {
423 logger.info(format(" Initializing two topologies for each window."));
424 } else {
425 logger.info(format(" Initializing a single topology for each window."));
426 }
427
428
429 for (int i = 0; i < numTopologies; i++) {
430 molecularAssemblies[i] = alchemicalOptions.openFile(algorithmFunctions, topologyOptions,
431 threadsPerTopology, files[i].getName(), i);
432 openers[i] = algorithmFunctions.getFilter();
433 }
434
435 StringBuilder sb = new StringBuilder(format(
436 "\n Performing FEP evaluations for: %s\n ", Arrays.toString(files)));
437 CrystalPotential potential = (CrystalPotential) topologyOptions.assemblePotential(molecularAssemblies, sb);
438 String[] arcFileName = new String[files.length];
439 for (int j = 0; j < numTopologies; j++) {
440
441 arcFileName[j] = FilenameUtils.removeExtension(files[j].getAbsolutePath()) + ".arc";
442 File archiveFile = new File(arcFileName[j]);
443 openers[j].setFile(archiveFile);
444 molecularAssemblies[j].setFile(archiveFile);
445 }
446
447
448 int nSnapshots = openers[0].countNumModels();
449 double[] x = new double[potential.getNumberOfVariables()];
450 double[] lambdaValues = new double[nLambda];
451 double[][] energy = new double[nLambda][nSnapshots];
452 double[][] lambdaDerivatives = new double[nLambda][nSnapshots];
453 for (int k = 0; k < lambdaValues.length; k++) {
454 lambdaValues[k] = (double) k / (nLambda - 1);
455 energy[k] = new double[nSnapshots];
456 lambdaDerivatives[k] = new double[nSnapshots];
457 }
458 LambdaInterface linter1 = (LambdaInterface) potential;
459 logger.info(format("\n\n Performing energy evaluations for %d snapshots.", nSnapshots));
460 logger.info(format(" Using %d lambda values.", nLambda));
461 logger.info(format(" Using %d topologies.", numTopologies));
462 logger.info(" Lambda values: " + Arrays.toString(lambdaValues));
463 logger.info("");
464 for (int i = 0; i < nSnapshots; i++) {
465
466 boolean resetPosition = (i == 0);
467 for (int n = 0; n < openers.length; n++) {
468 openers[n].readNext(resetPosition, false);
469 }
470 x = potential.getCoordinates(x);
471
472
473 StringBuilder sb2 = new StringBuilder().append("Snapshot ").append(i).append(" Energy Evaluations: ");
474 StringBuilder sb3 = new StringBuilder().append("Snapshot ").append(i).append(" Lambda Derivatives: ");
475 for (int k = 0; k < lambdaValues.length; k++) {
476 double lambda = lambdaValues[k];
477 if (lambda <= 1E-6) {
478 lambda += .00275;
479 }
480 if (lambda - 1.0 < 1E-6) {
481 lambda -= .00275;
482 }
483 linter1.setLambda(lambda);
484 energy[k][i] = potential.energyAndGradient(x, new double[x.length * 3]);
485 if (lambdaDerivative) {
486 lambdaDerivatives[k][i] = linter1.getdEdL();
487 sb3.append(" ").append(lambdaDerivatives[k][i]);
488 }
489 sb2.append(" ").append(energy[k][i]);
490 }
491 logger.info(sb2.append("\n").toString());
492 if (lambdaDerivative) {
493 logger.info(sb3.append("\n").toString());
494 }
495 }
496
497 return new double[][][]{energy, lambdaDerivatives};
498 }
499
500
501
502
503 @Override
504 public List<Potential> getPotentials() {
505 return Collections.emptyList();
506 }
507 }