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.numerics.estimator;
39
40 import static ffx.numerics.estimator.EstimateBootstrapper.getBootstrapIndices;
41 import static ffx.numerics.estimator.Zwanzig.Directionality.BACKWARDS;
42 import static ffx.numerics.estimator.Zwanzig.Directionality.FORWARDS;
43 import static ffx.numerics.math.ScalarMath.fermiFunction;
44 import static java.lang.String.format;
45 import static java.util.Arrays.copyOf;
46 import static java.util.Arrays.fill;
47 import static java.util.Arrays.stream;
48 import static org.apache.commons.math3.util.FastMath.abs;
49 import static org.apache.commons.math3.util.FastMath.log;
50 import static org.apache.commons.math3.util.FastMath.sqrt;
51
52 import ffx.numerics.math.SummaryStatistics;
53 import ffx.utilities.Constants;
54
55 import java.util.Random;
56 import java.util.logging.Level;
57 import java.util.logging.Logger;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public class BennettAcceptanceRatio extends SequentialEstimator implements BootstrappableEstimator {
80
81 private static final Logger logger = Logger.getLogger(BennettAcceptanceRatio.class.getName());
82
83
84
85
86 private static final double DEFAULT_TOLERANCE = 1.0E-7;
87
88
89
90 private static final int MAX_ITERS = 100;
91
92
93
94 private final int nWindows;
95
96
97
98 private final double[] forwardZwanzig;
99
100
101
102 private final double[] backwardZwanzig;
103
104
105
106 private final double[] barEstimates;
107
108
109
110 private final double[] barUncertainties;
111
112
113
114 private final double tolerance;
115
116
117
118 private final Zwanzig forwardsFEP;
119
120
121
122 private final Zwanzig backwardsFEP;
123 private final Random random;
124
125
126
127 private double totalBAREstimate;
128
129
130
131 private double totalBARUncertainty;
132
133
134
135 private final double[] barEnthalpy;
136
137
138
139 private double alpha;
140
141
142
143 private double fbsum;
144
145
146
147
148
149
150
151
152
153
154 public BennettAcceptanceRatio(double[] lambdaValues, double[][] energiesLow, double[][] energiesAt,
155 double[][] energiesHigh, double[] temperature) {
156 this(lambdaValues, energiesLow, energiesAt, energiesHigh, temperature, DEFAULT_TOLERANCE);
157 }
158
159
160
161
162
163
164
165
166
167
168
169 public BennettAcceptanceRatio(double[] lambdaValues, double[][] energiesLow, double[][] energiesAt,
170 double[][] energiesHigh, double[] temperature, double tolerance) {
171
172 super(lambdaValues, energiesLow, energiesAt, energiesHigh, temperature);
173
174
175 forwardsFEP = new Zwanzig(lambdaValues, energiesLow, energiesAt, energiesHigh, temperature, FORWARDS);
176 backwardsFEP = new Zwanzig(lambdaValues, energiesLow, energiesAt, energiesHigh, temperature, BACKWARDS);
177
178 nWindows = nTrajectories - 1;
179 forwardZwanzig = forwardsFEP.getBinEnergies();
180 backwardZwanzig = backwardsFEP.getBinEnergies();
181
182 barEstimates = new double[nWindows];
183 barUncertainties = new double[nWindows];
184 barEnthalpy = new double[nWindows];
185 this.tolerance = tolerance;
186 random = new Random();
187
188 estimateDG();
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203 private static void fermiDiffIterative(double[] e0, double[] e1, double[] fermiDiffs, int len,
204 double c, double invRT) {
205 for (int i = 0; i < len; i++) {
206 fermiDiffs[i] = fermiFunction(invRT * (e0[i] - e1[i] + c));
207 }
208 }
209
210
211
212
213
214
215
216
217
218
219 private void calcAlphaForward(double[] e0, double[] e1, int len, double c, double invRT) {
220
221 double fsum = 0;
222 double fvsum = 0;
223 double fbvsum = 0;
224 double vsum = 0;
225 fbsum = 0;
226 for (int i = 0; i < len; i++) {
227 double fore = fermiFunction(invRT * (e1[i] - e0[i] - c));
228 double back = fermiFunction(invRT * (e0[i] - e1[i] + c));
229 fsum += fore;
230 fvsum += fore * e0[i];
231 fbvsum += fore * back * (e1[i] - e0[i]);
232 vsum += e0[i];
233 fbsum += fore * back;
234 }
235 alpha = fvsum - (fsum * (vsum / len)) + fbvsum;
236 }
237
238
239
240
241
242
243
244
245
246
247 private void calcAlphaBackward(double[] e0, double[] e1, int len, double c, double invRT) {
248
249 double bsum = 0;
250 double bvsum = 0;
251 double fbvsum = 0;
252 double vsum = 0;
253 fbsum = 0;
254 for (int i = 0; i < len; i++) {
255 double fore = fermiFunction(invRT * (e1[i] - e0[i] - c));
256 double back = fermiFunction(invRT * (e0[i] - e1[i] + c));
257 bsum += back;
258 bvsum += back * e1[i];
259 fbvsum += fore * back * (e1[i] - e0[i]);
260 vsum += e1[i];
261 fbsum += fore * back;
262 }
263 alpha = bvsum - (bsum * (vsum / len)) - fbvsum;
264 }
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280 private static void fermiDiffBootstrap(double[] e0, double[] e1, double[] fermiDiffs,
281 int len, double c, double invRT, int[] bootstrapSamples) {
282 for (int indexI = 0; indexI < len; indexI++) {
283 int i = bootstrapSamples[indexI];
284 fermiDiffs[indexI] = fermiFunction(invRT * (e0[i] - e1[i] + c));
285 }
286 }
287
288
289
290
291
292
293
294
295
296
297 private static double uncertaintyCalculation(double meanFermi, double meanSqFermi, int len) {
298 double sqMeanFermi = meanFermi * meanFermi;
299 return ((meanSqFermi - sqMeanFermi) / len) / sqMeanFermi;
300 }
301
302
303
304
305
306
307 public Zwanzig getInitialBackwardsGuess() {
308 return backwardsFEP;
309 }
310
311
312
313
314
315 @Override
316 public final void estimateDG() {
317 estimateDG(false);
318 }
319
320
321
322
323
324
325 public Zwanzig getInitialForwardsGuess() {
326 return forwardsFEP;
327 }
328
329
330
331
332 @Override
333 public BennettAcceptanceRatio copyEstimator() {
334 return new BennettAcceptanceRatio(lamValues, eLow, eAt, eHigh, temperatures, tolerance);
335 }
336
337
338
339
340
341
342
343 @Override
344 public final void estimateDG(final boolean randomSamples) {
345 double cumDG = 0;
346 fill(barEstimates, 0);
347 fill(barUncertainties, 0);
348 fill(barEnthalpy, 0);
349
350
351 Level warningLevel = randomSamples ? Level.FINE : Level.WARNING;
352
353 for (int i = 0; i < nWindows; i++) {
354
355 double c = 0.5 * (forwardZwanzig[i] + backwardZwanzig[i]);
356
357 if (!randomSamples) {
358 logger.fine(format(" BAR Iteration %2d: %12.4f Kcal/mol", 0, c));
359 }
360
361 double cold = c;
362 int len0 = eAt[i].length;
363 int len1 = eAt[i + 1].length;
364
365 if (len0 == 0 || len1 == 0) {
366 barEstimates[i] = c;
367 logger.log(warningLevel, format(" Window %d has no snapshots at one end (%d, %d)!", i, len0, len1));
368 continue;
369 }
370
371
372 double sampleRatio = ((double) len0) / ((double) len1);
373
374
375 double[] fermi0 = new double[len0];
376 double[] fermi1 = new double[len1];
377
378
379 double rta = Constants.R * temperatures[i];
380 double rtb = Constants.R * temperatures[i + 1];
381 double rtMean = 0.5 * (rta + rtb);
382 double invRTA = 1.0 / rta;
383 double invRTB = 1.0 / rtb;
384
385
386 SummaryStatistics s1 = null;
387
388 SummaryStatistics s0 = null;
389
390
391 int[] bootstrapSamples0 = null;
392 int[] bootstrapSamples1 = null;
393
394 if (randomSamples) {
395 bootstrapSamples0 = getBootstrapIndices(len0, random);
396 bootstrapSamples1 = getBootstrapIndices(len1, random);
397 }
398
399 int cycleCounter = 0;
400 boolean converged = false;
401 while (!converged) {
402 if (randomSamples) {
403 fermiDiffBootstrap(eHigh[i], eAt[i], fermi0, len0, -c, invRTA, bootstrapSamples0);
404 fermiDiffBootstrap(eLow[i + 1], eAt[i + 1], fermi1, len1, c, invRTB, bootstrapSamples1);
405 } else {
406 fermiDiffIterative(eHigh[i], eAt[i], fermi0, len0, -c, invRTA);
407 fermiDiffIterative(eLow[i + 1], eAt[i + 1], fermi1, len1, c, invRTB);
408 }
409
410 s0 = new SummaryStatistics(fermi0);
411 s1 = new SummaryStatistics(fermi1);
412 double ratio = stream(fermi1).sum() / stream(fermi0).sum();
413
414 c += rtMean * log(sampleRatio * ratio);
415
416 converged = (abs(c - cold) < tolerance);
417 cold = c;
418
419 if (++cycleCounter > MAX_ITERS) {
420 throw new IllegalArgumentException(
421 format(" BAR required too many iterations (%d) to converge!", cycleCounter));
422 }
423
424 if (!randomSamples) {
425 logger.fine(format(" BAR Iteration %2d: %12.4f Kcal/mol", cycleCounter, c));
426 }
427 }
428
429 barEstimates[i] = c;
430 cumDG += c;
431 double sqFermiMean0 = new SummaryStatistics(
432 stream(fermi0).map((double d) -> d * d).toArray()).mean;
433 double sqFermiMean1 = new SummaryStatistics(
434 stream(fermi1).map((double d) -> d * d).toArray()).mean;
435 barUncertainties[i] = sqrt(uncertaintyCalculation(s0.mean, sqFermiMean0, len0)
436 + uncertaintyCalculation(s1.mean, sqFermiMean1, len1));
437
438 calcAlphaForward(eAt[i], eHigh[i], len0, c, invRTA);
439 double alpha0 = alpha;
440 double fbsum0 = fbsum;
441
442 calcAlphaBackward(eLow[i + 1], eAt[i + 1], len1, c, invRTB);
443 double alpha1 = alpha;
444 double fbsum1 = fbsum;
445
446 double hBar = (alpha0 - alpha1) / (fbsum0 + fbsum1);
447 barEnthalpy[i] = hBar;
448 }
449
450 totalBAREstimate = cumDG;
451 totalBARUncertainty = sqrt(stream(barUncertainties).map((double d) -> d * d).sum());
452 }
453
454
455
456
457 @Override
458 public double[] getBinEnergies() {
459 return copyOf(barEstimates, nWindows);
460 }
461
462
463
464
465 @Override
466 public double[] getBinUncertainties() {
467 return copyOf(barUncertainties, nWindows);
468 }
469
470
471
472
473 @Override
474 public double getFreeEnergy() {
475 return totalBAREstimate;
476 }
477
478
479
480
481 @Override
482 public double getUncertainty() {
483 return totalBARUncertainty;
484 }
485
486
487
488
489 @Override
490 public int numberOfBins() {
491 return nWindows;
492 }
493
494
495
496
497 @Override
498 public double[] getBinEnthalpies() {
499 return barEnthalpy;
500 }
501 }