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.ParallelStateEnergy;
41 import ffx.algorithms.cli.AlgorithmsCommand;
42 import ffx.crystal.Crystal;
43 import ffx.crystal.CrystalPotential;
44 import ffx.numerics.Potential;
45 import ffx.numerics.estimator.FreeEnergyDifferenceReporter;
46 import ffx.potential.MolecularAssembly;
47 import ffx.potential.cli.AlchemicalOptions;
48 import ffx.potential.cli.TopologyOptions;
49 import ffx.potential.parsers.BARFilter;
50 import ffx.potential.parsers.SystemFilter;
51 import ffx.utilities.FFXBinding;
52 import org.apache.commons.configuration2.Configuration;
53 import org.apache.commons.io.FilenameUtils;
54 import picocli.CommandLine.Command;
55 import picocli.CommandLine.Mixin;
56 import picocli.CommandLine.Option;
57 import picocli.CommandLine.Parameters;
58
59 import java.io.File;
60 import java.io.IOException;
61 import java.nio.file.Files;
62 import java.nio.file.Path;
63 import java.nio.file.Paths;
64 import java.util.ArrayList;
65 import java.util.Collections;
66 import java.util.Comparator;
67 import java.util.List;
68 import java.util.regex.Matcher;
69 import java.util.regex.Pattern;
70 import java.util.stream.Stream;
71
72 import static java.lang.String.format;
73
74
75
76
77
78
79
80
81
82
83 @Command(description = " Evaluates a free energy change with the Bennett Acceptance Ratio algorithm using pregenerated snapshots.", name = "BAR")
84 public class BAR extends AlgorithmsCommand {
85
86 @Mixin
87 private AlchemicalOptions alchemicalOptions;
88
89 @Mixin
90 private TopologyOptions topologyOptions;
91
92
93
94
95 @Option(names = {"--eb", "--evaluateBAR"}, paramLabel = "false", defaultValue = "false",
96 description = "Evaluate Free Energy Differences from Tinker BAR files.")
97 private boolean evaluateBARFiles = false;
98
99 @Option(names = {"--l2", "--lambdaTwo"}, paramLabel = "1.0",
100 description = "Lambda value for the upper edge of the window")
101 private double lambda2 = 1.0;
102
103 @Option(names = {"-t", "--temperature"}, paramLabel = "298.15",
104 description = "Temperature for system")
105 private double temperature = 298.15;
106
107 @Option(names = {"--dV", "--volume"}, paramLabel = "false",
108 description = "Write out snapshot volumes to the Tinker BAR file.")
109 private boolean includeVolume = false;
110
111 @Option(names = {"--ns", "--nStates"}, paramLabel = "2",
112 description = "If not equal to two, auto-determine lambda values and subdirectories (overrides other flags).")
113 private int nStates = 2;
114
115 @Option(names = {"--sa", "--sortedArc"}, paramLabel = "false",
116 description = "If set, use sorted archive values.")
117 private boolean sortedArc = false;
118
119 @Option(names = {"--ss", "--startSnapshot"}, paramLabel = "0",
120 description = "Start at this snapshot when reading in Tinker BAR files (indexed from 0).")
121 private int startingSnapshot = 0;
122
123 @Option(names = {"--es", "--endSnapshot"}, paramLabel = "0",
124 description = "End at this snapshot when reading in Tinker BAR files (indexed from 0).")
125 private int endingSnapshot = 0;
126
127
128
129
130 @Option(names = {"--ni", "--nIterations"}, paramLabel = "1000",
131 description = "Specify the maximum number of iterations for BAR convergence.")
132 private int nIterations = 1000;
133
134
135
136
137 @Option(names = {"-e", "--eps"}, paramLabel = "1.0E-4",
138 description = "Specify convergence cutoff for BAR calculation.")
139 private double eps = 1.0E-4;
140
141
142
143
144 @Parameters(arity = "0..*", paramLabel = "files",
145 description = "A single PDB/XYZ when windows are auto-determined (or two for dual topology). Two trajectory files for BAR between two ensembles (or four for dual topology).")
146 private List<String> filenames = null;
147
148
149
150
151 private Configuration additionalProperties;
152
153
154
155
156 MolecularAssembly[] topologies;
157
158
159
160
161 private int numTopologies;
162
163
164
165
166 CrystalPotential potential;
167
168
169
170
171 int nFiles;
172
173
174
175
176 private String[] files;
177
178
179
180
181 double[] lambdaValues;
182
183
184
185
186 private FreeEnergyDifferenceReporter reporter;
187
188
189
190
191 public BAR() {
192 super();
193 }
194
195
196
197
198
199
200 public BAR(FFXBinding binding) {
201 super(binding);
202 }
203
204
205
206
207
208
209 public BAR(String[] args) {
210 super(args);
211 }
212
213
214
215
216
217
218 public void setProperties(Configuration additionalProps) {
219 this.additionalProperties = additionalProps;
220 }
221
222
223
224
225 @Override
226 public BAR run() {
227
228 if (!init()) {
229 return this;
230 }
231
232
233
234
235 if (filenames == null) {
236 nFiles = 0;
237 files = null;
238 } else {
239 nFiles = filenames.size();
240 files = new String[nFiles];
241 for (int i = 0; i < nFiles; i++) {
242 files[i] = filenames.get(i);
243 }
244 }
245
246
247 if (evaluateBARFiles) {
248
249
250
251 if (nFiles == 0) {
252 logger.info(" Please supply a BAR file or directory to be evaluated.");
253 return this;
254 }
255
256 BARFilter[] barFilters = null;
257 if (nStates == 2 && nFiles == 1) {
258
259 logger.info(format(" Evaluating a single BAR file: %s.", files[0]));
260 barFilters = new BARFilter[1];
261 barFilters[0] = new BARFilter(new File(files[0]), startingSnapshot, endingSnapshot);
262
263 lambdaValues = new double[2];
264 lambdaValues[0] = alchemicalOptions.getInitialLambda();
265 lambdaValues[1] = lambda2;
266 } else if (nStates > 2 && nFiles == 1) {
267
268 logger.info(format(" Evaluating BAR files from directory %s.", files[0]));
269
270 logger.info(" Auto-detecting BAR files and lambda values.");
271 Path subdirectoryPath = Paths.get(files[0]);
272
273 Pattern pattern = Pattern.compile(".*_(\\d+)\\.bar");
274
275 List<String> fileList = new ArrayList<>();
276 try (Stream<Path> paths = Files.list(subdirectoryPath)) {
277 paths.filter(Files::isRegularFile)
278 .filter(path -> {
279 Matcher matcher = pattern.matcher(path.getFileName().toString());
280 return matcher.matches();
281 })
282 .sorted(Comparator.comparingInt(path -> {
283 Matcher matcher = pattern.matcher(path.getFileName().toString());
284 matcher.matches();
285 return Integer.parseInt(matcher.group(1));
286 }))
287 .forEach(path -> fileList.add(path.toString()));
288 } catch (IOException e) {
289 logger.info(" Error reading files from a directory.\n" + e.toString());
290 e.printStackTrace();
291 return this;
292 }
293
294 int numFiles = fileList.size();
295 if (numFiles != nStates - 1) {
296 logger.info(format(" The number of bar files (%d) does not concord with the number of states (%d).", numFiles, nStates));
297 return this;
298 }
299
300
301 lambdaValues = new double[nStates];
302 for (int l = 0; l < nStates; l++) {
303 lambdaValues[l] = alchemicalOptions.getInitialLambda(nStates, l, true);
304 }
305 barFilters = new BARFilter[numFiles];
306 for (int i = 0; i < numFiles; i++) {
307 String filename = fileList.get(i);
308 File barFile = new File(filename);
309 barFilters[i] = new BARFilter(barFile, startingSnapshot, endingSnapshot);
310 logger.info(format(" Window L=%6.4f to L=%6.4f from %s",
311 lambdaValues[i], lambdaValues[i + 1], filename));
312 }
313 }
314
315
316 double[][] energiesLow = new double[nStates][];
317 double[][] energiesAt = new double[nStates][];
318 double[][] energiesHigh = new double[nStates][];
319 double[][] volume = new double[nStates][];
320 double[] temperatures = new double[nStates];
321
322
323 readBARFiles(barFilters, energiesLow, energiesAt, energiesHigh, volume, temperatures);
324
325
326 reporter = new FreeEnergyDifferenceReporter(nStates, lambdaValues, temperatures, eps, nIterations,
327 energiesLow, energiesAt, energiesHigh, volume);
328 reporter.report();
329
330
331 return this;
332 }
333
334
335 logger.info(" Writing BAR files.");
336
337 numTopologies = 1;
338 if (nFiles <= 1 && nStates < 2) {
339 logger.info(" At least two states must be specified");
340 return this;
341 } else if (nFiles == 1 && nStates >= 2) {
342 logger.info(format(" Auto-detecting %d states for single topology:\n %s.", nStates, files[0]));
343 } else if (nFiles == 2 && nStates >= 2) {
344 logger.info(format(" Auto-detecting %d states for dual topology:\n %s\n %s.", nStates, files[0], files[1]));
345 numTopologies = 2;
346 } else if (nFiles == 2) {
347 logger.info(format(" Applying BAR between two single topology ensembles:\n %s\n %s.", files[0], files[1]));
348 nStates = 2;
349 } else if (nFiles == 4) {
350 logger.info(format(" Applying BAR between two dual topology ensembles:\n %s %s\n %s %s.",
351 files[0], files[1], files[2], files[3]));
352 numTopologies = 2;
353 nStates = 2;
354 } else {
355 logger.info(format(" Inconsistent input of files (%3d) and/or states (%3d).", nFiles, nStates));
356 return this;
357 }
358
359 boolean autodetect = false;
360 if (nStates > 2) {
361 autodetect = true;
362 lambdaValues = new double[nStates];
363 for (int i = 0; i < nStates; i++) {
364 lambdaValues[i] = alchemicalOptions.getInitialLambda(nStates, i, true);
365 }
366 } else {
367
368 lambdaValues = new double[2];
369 lambdaValues[0] = alchemicalOptions.getInitialLambda(2, 0, true);
370 lambdaValues[1] = lambda2;
371 nStates = 2;
372 }
373
374
375 logger.info(" Lambda values for each window: ");
376 int nLambda = lambdaValues.length;
377 for (int i = 0; i < nLambda; i++) {
378 logger.info(format(" Window %3d: %6.4f", i, lambdaValues[i]));
379 }
380
381
382 boolean isPBC;
383
384
385 int threadsPerTopology = topologyOptions.getThreadsPerTopology(numTopologies);
386 topologies = new MolecularAssembly[numTopologies];
387 SystemFilter[] openers = new SystemFilter[numTopologies];
388
389 alchemicalOptions.setAlchemicalProperties();
390 topologyOptions.setAlchemicalProperties(numTopologies);
391
392 if (numTopologies == 2) {
393 logger.info(format(" Initializing two topologies for each window."));
394 } else {
395 logger.info(format(" Initializing a single topology for each window."));
396 }
397
398 for (int i = 0; i < numTopologies; i++) {
399 MolecularAssembly ma = alchemicalOptions.openFile(algorithmFunctions, topologyOptions,
400 threadsPerTopology, filenames.get(i), i);
401 topologies[i] = ma;
402 openers[i] = algorithmFunctions.getFilter();
403 }
404
405 StringBuilder sb = new StringBuilder(format(
406 "\n Using BAR to analyze a free energy change for %s\n ", filenames));
407 potential = (CrystalPotential) topologyOptions.assemblePotential(topologies, sb);
408 Crystal unitCell = potential.getCrystal().getUnitCell();
409 isPBC = includeVolume && !unitCell.aperiodic();
410
411 String[][] fullFilePaths;
412 String directoryPath;
413 if (nFiles > 0) {
414
415
416 int dtIndex = 0;
417 if (numTopologies == 2 && nFiles == 4) {
418 fullFilePaths = new String[nStates][2];
419 dtIndex = 2;
420 } else {
421 fullFilePaths = new String[nStates][nFiles];
422 }
423
424
425 for (int i = 0; i < nStates; i++) {
426
427 for (int j = 0; j < nFiles; j++) {
428
429 if (i == 1 && j == 0) {
430 j = dtIndex;
431 }
432 File file = new File(files[j]);
433 directoryPath = file.getAbsoluteFile().getParent() + File.separator;
434 String archiveName;
435 if (sortedArc) {
436 archiveName = FilenameUtils.getBaseName(files[j]) + "_E" + String.valueOf(i) + ".arc";
437 } else {
438 archiveName = FilenameUtils.getBaseName(files[j]) + ".arc";
439 }
440 if (!autodetect) {
441
442 fullFilePaths[i][j - dtIndex] = directoryPath + File.separator + archiveName;
443 } else {
444
445 fullFilePaths[i][j - dtIndex] = directoryPath + i + File.separator + archiveName;
446 }
447
448 if (i == 0 && j == 1) {
449 j += dtIndex * nFiles;
450 }
451 }
452 }
453
454
455 ParallelStateEnergy pe = new ParallelStateEnergy(nStates, lambdaValues,
456 topologies, potential, fullFilePaths, openers);
457
458
459 double[][] energiesLow = new double[nStates][];
460 double[][] energiesAt = new double[nStates][];
461 double[][] energiesHigh = new double[nStates][];
462 double[][] volume = new double[nStates][];
463 pe.evaluateStates(energiesLow, energiesAt, energiesHigh, volume);
464
465
466 if (pe.getRank() != 0) {
467 return this;
468 }
469
470
471 File file = new File(files[0]);
472 directoryPath = file.getAbsoluteFile().getParent() + File.separator;
473 String tinkerDirectoryPath = directoryPath + File.separator + "windows";
474 File directory = new File(tinkerDirectoryPath);
475 String barFilePath = tinkerDirectoryPath + File.separator;
476 directory.mkdir();
477
478 for (int state = 0; state < nStates - 1; state++) {
479 File xyzFile = new File(filenames.get(0));
480 BARFilter barFilter;
481 barFilter = new BARFilter(xyzFile, energiesAt[state], energiesHigh[state], energiesLow[state + 1],
482 energiesAt[state + 1], volume[state], volume[state + 1], temperature, temperature);
483 String barFileName = barFilePath + "window_" + String.valueOf(state) + ".bar";
484
485 barFilter.writeFile(barFileName, isPBC, false);
486 }
487 }
488
489 return this;
490 }
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506 void readBARFiles(BARFilter[] barFilters,
507 double[][] energyLow, double[][] energyAt,
508 double[][] energyHigh, double[][] volume,
509 double[] temperatures) {
510
511
512 for (BARFilter barFilter : barFilters) {
513 barFilter.readFile();
514 }
515
516
517 for (int state = 0; state < nStates; state++) {
518 if (state == 0) {
519 energyAt[state] = barFilters[state].getE1l1();
520 energyHigh[state] = barFilters[state].getE1l2();
521 volume[state] = barFilters[state].getVolume1();
522 temperatures[state] = barFilters[state].getTemperature1();
523 } else if (state == nStates - 1) {
524 energyLow[state] = barFilters[state - 1].getE2l1();
525 energyAt[state] = barFilters[state - 1].getE2l2();
526 volume[state] = barFilters[state - 1].getVolume2();
527 temperatures[state] = barFilters[state - 1].getTemperature2();
528 } else {
529 energyLow[state] = barFilters[state - 1].getE2l1();
530 energyAt[state] = barFilters[state].getE1l1();
531 energyHigh[state] = barFilters[state].getE1l2();
532 volume[state] = barFilters[state].getVolume1();
533 temperatures[state] = barFilters[state].getTemperature1();
534 }
535 }
536 }
537
538
539
540
541 @Override
542 public List<Potential> getPotentials() {
543 List<Potential> potentials;
544 if (potential == null) {
545 potentials = Collections.emptyList();
546 } else {
547 potentials = new ArrayList<>();
548 potentials.add(potential);
549 }
550 return potentials;
551 }
552
553
554
555
556
557
558 public FreeEnergyDifferenceReporter getReporter() {
559 return reporter;
560 }
561
562 }