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 }