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.numerics.estimator;
39  
40  import static java.lang.String.format;
41  import static java.util.Arrays.stream;
42  
43  import ffx.numerics.math.RunningStatistics;
44  import ffx.numerics.math.SummaryStatistics;
45  import java.util.Random;
46  import java.util.concurrent.ThreadLocalRandom;
47  import java.util.logging.Logger;
48  
49  /**
50   * Bootstrap Free Energy Estimate.
51   */
52  public class EstimateBootstrapper {
53  
54    private static final Logger logger = Logger.getLogger(EstimateBootstrapper.class.getName());
55    private static final long DEFAULT_LOG_INTERVAL = 25;
56  
57    private final BootstrappableEstimator estimate;
58    private final int nWindows;
59    private final SummaryStatistics[] bootstrapFEResults;
60    private final SummaryStatistics[] bootstrapEnthalpyResults;
61  
62    public EstimateBootstrapper(BootstrappableEstimator estimator) {
63      this.estimate = estimator;
64      nWindows = estimate.numberOfBins();
65      bootstrapFEResults = new SummaryStatistics[nWindows];
66      bootstrapEnthalpyResults = new SummaryStatistics[nWindows];
67    }
68  
69    /**
70     * Gets randomized bootstrap indices; ensures there are at least two distinct indices.
71     *
72     * @param length Number of random indices to generate in range [0,length)
73     * @return Randomized indices.
74     */
75    public static int[] getBootstrapIndices(int length) {
76      return getBootstrapIndices(length, ThreadLocalRandom.current());
77    }
78  
79    /**
80     * Gets randomized bootstrap indices; ensures there are at least two distinct indices.
81     *
82     * @param length Number of random indices to generate in range [0,length)
83     * @param random Source of randomness.
84     * @return Randomized indices.
85     */
86    public static int[] getBootstrapIndices(int length, Random random) {
87      return getBootstrapIndices(length, random, Math.min(2, length));
88    }
89  
90    /**
91     * Gets randomized bootstrap indices; ensures there are at least a few distinct indices.
92     *
93     * @param length Number of random indices to generate in range [0,length)
94     * @param random Source of randomness.
95     * @param minDistinct Minimum number of distinct indices.
96     * @return Randomized indices.
97     */
98    public static int[] getBootstrapIndices(int length, Random random, int minDistinct) {
99      // Handle extremely short lengths with special-case handling.
100     switch (length) {
101       case 0 -> {
102         return new int[0];
103       }
104       case 1 -> {
105         return new int[] {0};
106       }
107       case 2 -> {
108         int[] indices = new int[2];
109         indices[0] = random.nextBoolean() ? 0 : 1;
110         indices[1] = random.nextBoolean() ? 0 : 1;
111         return indices;
112       }
113       // Default: leave switch and handle general case.
114     }
115 
116     // General case.
117     int[] indices = random.ints(length, 0, length).toArray();
118     long distinctVal = stream(indices).distinct().count();
119     int ctr = 0;
120     while (distinctVal <= minDistinct) {
121       logger.info(
122           format(" Regenerating array (iteration %d): only %d distinct values found for length %d.",
123               ++ctr, distinctVal, length));
124       indices = random.ints(length, 0, length).toArray();
125       distinctVal = stream(indices).distinct().count();
126     }
127     return indices;
128   }
129 
130   /**
131    * Get Bootstrap Free Energy results for each window.
132    *
133    * @return Bootstrap Enthalpy results for each window.
134    */
135   public SummaryStatistics[] getBootstrapEnthalpyResults() {
136     return bootstrapEnthalpyResults;
137   }
138 
139   /**
140    * Perform bootstrap analysis.
141    *
142    * @param trials Number of trials.
143    */
144   public void bootstrap(long trials) {
145     bootstrap(trials, DEFAULT_LOG_INTERVAL);
146   }
147 
148   /**
149    * Perform bootstrap analysis.
150    *
151    * @param trials Number of trials.
152    * @param logInterval Interval between logging statements.
153    */
154   public void bootstrap(long trials, long logInterval) {
155     RunningStatistics[] windows = new RunningStatistics[nWindows];
156     RunningStatistics[] enthalpyWindows = new RunningStatistics[nWindows];
157     for (int i = 0; i < nWindows; i++) {
158       windows[i] = new RunningStatistics();
159       enthalpyWindows[i] = new RunningStatistics();
160     }
161 
162     for (long i = 0; i < trials; i++) {
163       if ((i + 1) % logInterval == 0) {
164         logger.fine(format(" Bootstrap Trial %d", i + 1));
165       }
166 
167       estimate.estimateDG(true);
168 
169       double[] fe = estimate.getBinEnergies();
170       double[] enthalpy = estimate.getBinEnthalpies();
171       for (int j = 0; j < nWindows; j++) {
172         windows[j].addValue(fe[j]);
173         enthalpyWindows[j].addValue(enthalpy[j]);
174       }
175     }
176 
177     for (int i = 0; i < nWindows; i++) {
178       bootstrapFEResults[i] = new SummaryStatistics(windows[i]);
179       bootstrapEnthalpyResults[i] = new SummaryStatistics(enthalpyWindows[i]);
180     }
181   }
182 
183   /**
184    * Get bootstrap free energy estimate for each window.
185    *
186    * @return Return the bootstrap free energy difference estimate for each window.
187    */
188   public double[] getFE() {
189     return stream(bootstrapFEResults).mapToDouble(SummaryStatistics::getMean).toArray();
190   }
191 
192   /**
193    * Get bootstrap enthalpy estimate for each window.
194    *
195    * @return Return the bootstrap enthalpy estimate for each window.
196    */
197   public double[] getEnthalpy() {
198     return stream(bootstrapEnthalpyResults).mapToDouble(SummaryStatistics::getMean).toArray();
199   }
200 
201   /**
202    * Get the total free energy difference estimate from bootstrap analysis.
203    *
204    * @return The total free energy difference estimate.
205    */
206   public double getTotalFE() {
207     return getTotalFE(getFE());
208   }
209 
210   /**
211    * Get the total enthalpy estimate from bootstrap analysis.
212    *
213    * @return The total enthalpy estimate.
214    */
215 
216   public double getTotalEnthalpy() {
217     return getTotalEnthalpy(getEnthalpy());
218   }
219 
220 
221   /**
222    * Get the total free energy difference estimate from per window bootstrap analysis.
223    *
224    * @param fe The free energy difference estimate for each window.
225    * @return The total free energy difference estimate from bootstrap analysis.
226    */
227   public double getTotalFE(double[] fe) {
228     return estimate.sumBootstrapResults(fe);
229   }
230 
231   /**
232    * Get the total enthalpy estimate from per window bootstrap analysis.
233    *
234    * @param enthalpy The enthalpy estimate for each window.
235    * @return The total enthalpy difference estimate from bootstrap analysis.
236    */
237   public double getTotalEnthalpy(double[] enthalpy) {
238     return estimate.sumBootstrapResults(enthalpy);
239   }
240 
241   /**
242    * Get the total free energy difference variance estimate from bootstrap analysis.
243    *
244    * @return The total free energy difference variance estimate.
245    */
246   public double getTotalUncertainty() {
247     return getTotalUncertainty(getVariance());
248   }
249 
250   /**
251    * Get the total free energy difference estimate from per window bootstrap analysis.
252    *
253    * @param var The free energy difference variance estimate (not uncertainty) for each window.
254    * @return The total free energy difference variance estimate from bootstrap analysis.
255    */
256   public double getTotalUncertainty(double[] var) {
257     return estimate.sumBootstrapUncertainty(var);
258   }
259 
260   /**
261    * Get the total enthalpy variance estimate from bootstrap analysis.
262    *
263    * @return The total enthalpy variance estimate.
264    */
265   public double getTotalEnthalpyUncertainty() {
266     return getTotalEnthalpyUncertainty(getEnthalpyVariance());
267   }
268 
269   /**
270    * Get the total enthalpy estimate from per window bootstrap analysis.
271    *
272    * @param var The enthalpy variance estimate (not uncertainty) for each window.
273    * @return The total enthalpy variance estimate from bootstrap analysis.
274    */
275   public double getTotalEnthalpyUncertainty(double[] var) {
276     return estimate.sumBootstrapEnthalpyUncertainty(var);
277   }
278 
279   /**
280    * Get the free energy difference standard deviation estimate from bootstrap analysis for each
281    * window.
282    *
283    * @return Returns free energy difference standard deviation estimate for each window.
284    */
285   public double[] getUncertainty() {
286     return stream(bootstrapFEResults).mapToDouble(SummaryStatistics::getSd).toArray();
287   }
288 
289   /**
290    * Get the enthalpy standard deviation estimate from bootstrap analysis for each window.
291    *
292    * @return Returns enthalpy standard deviation estimate for each window.
293    */
294   public double[] getEnthalpyUncertainty() {
295     return stream(bootstrapEnthalpyResults).mapToDouble(SummaryStatistics::getSd).toArray();
296   }
297 
298   /**
299    * Get the free energy difference variance estimate from bootstrap analysis for each window.
300    *
301    * @return Returns free energy difference variance estimate for each window.
302    */
303   public double[] getVariance() {
304     return stream(bootstrapFEResults).mapToDouble(SummaryStatistics::getVar).toArray();
305   }
306 
307   /**
308    * Get the enthalpy variance estimate from bootstrap analysis for each window.
309    *
310    * @return Returns enthalpy variance estimate for each window.
311    */
312   public double[] getEnthalpyVariance() {
313     return stream(bootstrapEnthalpyResults).mapToDouble(SummaryStatistics::getVar).toArray();
314   }
315 }