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 int col = j;
441 if (dtIndex == 2) {
442 col = Math.floorMod(j - dtIndex, 2);
443 }
444 if (!autodetect) {
445
446 fullFilePaths[i][col] = directoryPath + File.separator + archiveName;
447 } else {
448
449 fullFilePaths[i][col] = directoryPath + i + File.separator + archiveName;
450 }
451
452 if (i == 0 && j == 1) {
453 j += dtIndex * nFiles;
454 }
455 }
456 }
457
458
459 ParallelStateEnergy pe = new ParallelStateEnergy(nStates, lambdaValues,
460 topologies, potential, fullFilePaths, openers);
461
462
463 double[][] energiesLow = new double[nStates][];
464 double[][] energiesAt = new double[nStates][];
465 double[][] energiesHigh = new double[nStates][];
466 double[][] volume = new double[nStates][];
467 pe.evaluateStates(energiesLow, energiesAt, energiesHigh, volume);
468
469
470 if (pe.getRank() != 0) {
471 return this;
472 }
473
474
475 File file = new File(files[0]);
476 directoryPath = file.getAbsoluteFile().getParent() + File.separator;
477 String tinkerDirectoryPath = directoryPath + File.separator + "windows";
478 File directory = new File(tinkerDirectoryPath);
479 String barFilePath = tinkerDirectoryPath + File.separator;
480 directory.mkdir();
481
482 for (int state = 0; state < nStates - 1; state++) {
483 File xyzFile = new File(filenames.get(0));
484 BARFilter barFilter;
485 barFilter = new BARFilter(xyzFile, energiesAt[state], energiesHigh[state], energiesLow[state + 1],
486 energiesAt[state + 1], volume[state], volume[state + 1], temperature, temperature);
487 String barFileName = barFilePath + "window_" + String.valueOf(state) + ".bar";
488
489 barFilter.writeFile(barFileName, isPBC, false);
490 }
491 }
492
493 return this;
494 }
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510 void readBARFiles(BARFilter[] barFilters,
511 double[][] energyLow, double[][] energyAt,
512 double[][] energyHigh, double[][] volume,
513 double[] temperatures) {
514
515
516 for (BARFilter barFilter : barFilters) {
517 barFilter.readFile();
518 }
519
520
521 for (int state = 0; state < nStates; state++) {
522 if (state == 0) {
523 energyAt[state] = barFilters[state].getE1l1();
524 energyHigh[state] = barFilters[state].getE1l2();
525 volume[state] = barFilters[state].getVolume1();
526 temperatures[state] = barFilters[state].getTemperature1();
527 } else if (state == nStates - 1) {
528 energyLow[state] = barFilters[state - 1].getE2l1();
529 energyAt[state] = barFilters[state - 1].getE2l2();
530 volume[state] = barFilters[state - 1].getVolume2();
531 temperatures[state] = barFilters[state - 1].getTemperature2();
532 } else {
533 energyLow[state] = barFilters[state - 1].getE2l1();
534 energyAt[state] = barFilters[state].getE1l1();
535 energyHigh[state] = barFilters[state].getE1l2();
536 volume[state] = barFilters[state].getVolume1();
537 temperatures[state] = barFilters[state].getTemperature1();
538 }
539 }
540 }
541
542
543
544
545 @Override
546 public List<Potential> getPotentials() {
547 List<Potential> potentials;
548 if (potential == null) {
549 potentials = Collections.emptyList();
550 } else {
551 potentials = new ArrayList<>();
552 potentials.add(potential);
553 }
554 return potentials;
555 }
556
557
558
559
560
561
562 public FreeEnergyDifferenceReporter getReporter() {
563 return reporter;
564 }
565
566 }