1 // ****************************************************************************** 2 // 3 // Title: Force Field X. 4 // Description: Force Field X - Software for Molecular Biophysics. 5 // Copyright: Copyright (c) Michael J. Schnieders 2001-2023. 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.algorithms.thermodynamics; 39 40 import org.apache.commons.configuration2.CompositeConfiguration; 41 42 import java.io.BufferedReader; 43 import java.io.File; 44 import java.io.FileReader; 45 import java.io.IOException; 46 47 /** 48 * HistogramSettings is a mutable settings class for OST histograms. Many fields in Histogram are (or 49 * should be) final at construction, but there are too many to reasonably put in a constructor. As 50 * such, HistogramSettings stores these values and their defaults, so a Histogram can have many final 51 * fields with a small-signature constructor. 52 * 53 * <p>While most fields are package-private and can be set easily (technically breaking 54 * encapsulation), some fields are fully private because they are involved in side effects. For 55 * example, dL may be adjusted slightly downwards if continuous lambda bins are in use, and we need 56 * to arrive at an odd number of lambda bins. 57 */ 58 public class HistogramSettings { 59 60 private static final boolean DEFAULT_DISCRETE_LAMBDA = false; 61 private static final double DEFAULT_DL = 0.005; 62 private static final double DEFAULT_DFL = 2.0; 63 private static final int DEFAULT_BIAS_CUTOFF = 5; 64 private static final double DEFAULT_BIAS_MAG = 0.05; 65 private static final double DEFAULT_TEMPERATURE = 298.15; // TODO: Consider setting this in Constants. 66 private static final double DEFAULT_TEMPERING_FACTOR = 2.0; 67 private static final int DEFAULT_FLAMDA_PRINT_INTERVAL = 25; 68 private static final double DEFAULT_TIMESTEP = 1.0; 69 private static final double DEFAULT_THETA_FRICTION = 1.0e-19; 70 private static final double DEFAULT_THETA_MASS = 1.0e-18; 71 private static final int DEFAULT_COUNT_INTERVAL = 10; 72 private static final double DEFAULT_LAMBDA_RESET = 0.99; 73 private static final boolean DEFAULT_RESET_STATISTICS = false; 74 private static final boolean DEFAULT_TEMPERING = true; 75 /** 76 * If true, use discrete lambda values instead of continuous lambda values. Is a final field to 77 * avoid strange interactions with dL and number of lambda bins. 78 */ 79 public final boolean discreteLambda; 80 /** 81 * Each walker reads the same histogram restart file. Only the walker of rank 0 writes the 82 * histogram restart file. 83 */ 84 private final File histogramFile; 85 /** 86 * Relative path to the histogram restart file. Assumption: a Histogram object will never change 87 * its histogram or lambda files. 88 */ 89 private final String hisFileName; 90 /** 91 * Relative path to the lambda restart file. Assumption: a Histogram object will never change its 92 * histogram or lambda files. 93 */ 94 private final String lambdaFileName; 95 /** 96 * Temperature in Kelvin. 97 * 98 * <p>The default is 298.15. 99 */ 100 public double temperature = DEFAULT_TEMPERATURE; 101 /** 102 * The Dama et al. transition-tempering rate parameter. A reasonable value is about 2 to 8 kT, with 103 * larger values being resulting in slower decay. 104 * 105 * <p>The default temperingFactor = 2.0. 106 */ 107 public double temperingFactor = DEFAULT_TEMPERING_FACTOR; 108 /** 109 * Time step in picoseconds. 110 */ 111 public double dt = DEFAULT_TIMESTEP; 112 /** 113 * Reasonable thetaFriction is ~60 per picosecond (1.0e-12). 114 */ 115 public double thetaFriction = DEFAULT_THETA_FRICTION; 116 /** 117 * Reasonable thetaMass is ~100 a.m.u. (100 a.m.u is 1.6605e-22 grams). 118 */ 119 public double thetaMass = DEFAULT_THETA_MASS; 120 /** 121 * Interval between adding a count to the Recursion kernel in MD steps. 122 * 123 * <p>The default countInterval = 10. 124 */ 125 public int countInterval = DEFAULT_COUNT_INTERVAL; 126 /** 127 * Flag to indicate if OST should send and receive counts between processes synchronously or 128 * asynchronously. The latter can be faster by ~40% because simulation with Lambda > 0.75 must 129 * compute two condensed phase self-consistent fields to interpolate polarization. 130 */ 131 public boolean asynchronous = true; 132 133 double dFL; 134 /** 135 * When evaluating the biasing potential, contributions from a Gaussian centered on bins more the 136 * "biasCutoff" away will be neglected. 137 * 138 * <p>The default biasCutoff = 5. 139 */ 140 int biasCutoff; 141 /** 142 * When evaluating the biasing potential, contributions from a Gaussian centered on bins more than 143 * "lambdaBiasCutoff" in the lambda dimension away will be neglected. 144 * 145 * <p>The default biasCutoff = 5 (continuous) or 0 (discrete). 146 */ 147 int lambdaBiasCutoff; 148 /** 149 * Interval between how often the 1D histogram is printed to screen versus silently updated in 150 * background. 151 * 152 * <p>The fLambdaPrintInterval is 25. 153 */ 154 final int fLambdaPrintInterval = DEFAULT_FLAMDA_PRINT_INTERVAL; 155 156 157 boolean tempering; 158 /** 159 * Width of standard lambda bins. Must be accessed by method because of an interaction with 160 * discreteLambda; if that field is false, the Histogram needs an odd number of bins, which may 161 * affect dL. 162 */ 163 private double dL; 164 /** 165 * Magnitude of each hill (not including tempering). Must be set by method because the default 166 * behavior for temperingThreshold is to be 20x this value. 167 * 168 * <p>The default biasMag = 0.05 (kcal/mol). 169 */ 170 private double biasMag; 171 172 private final double DEFAULT_BIAS_TO_OFFSET = 20.0; 173 /** 174 * An offset applied before calculating tempering weight. 175 * 176 * <p>First, for every Lambda, we calculate the maximum bias at that lambda by searching all 177 * populated dU/dL bins: maxdUdL(L) = max[ G(L,F_L) ] where the max operator searches over all F_L 178 * bins. 179 * 180 * <p>Then, the minimum bias coverage is determined by searching the maxdUdL(L) array over Lambda. 181 * minBias = min[ maxdUdL(L) ] where the min operator searches all entries in the array. 182 * 183 * <p>Then the temperOffset is applied to the minBias: 184 * 185 * <p>biasHeight = max[minBias - temperOffset, 0] 186 * 187 * <p>The default temperOffset = 1.0 kcal/mol. 188 * 189 * <p>Must be set/accessed programmatically due to the default being 20x the Gaussian bias 190 * magnitude. 191 */ 192 private double temperOffset = biasMag * DEFAULT_BIAS_TO_OFFSET; 193 // If set true (either by method or property setting temperOffset), no longer calculate based on 194 // bias magnitude. 195 private boolean temperOffsetSet = false; 196 /** 197 * Once the lambda reset value is reached, OST statistics are reset. 198 */ 199 private double lambdaResetValue = DEFAULT_LAMBDA_RESET; 200 /** 201 * Flag set to false once OST statistics are reset at lambdaResetValue. 202 */ 203 private boolean resetStatistics = DEFAULT_RESET_STATISTICS; 204 205 private boolean metaDynamics = false; 206 private boolean writeIndependent = false; 207 private boolean independentWalkers = false; 208 /** 209 * Flag indicating if a histogram file was read in. 210 */ 211 final boolean histogramRead; 212 213 public HistogramSettings(File hisFile, String lamFileName, CompositeConfiguration properties) 214 throws IOException { 215 this(hisFile, lamFileName, properties, 216 properties.getBoolean("discrete-lambda", DEFAULT_DISCRETE_LAMBDA)); 217 } 218 219 public HistogramSettings(File hisFile, String lamFileName, 220 CompositeConfiguration properties, boolean discreteLambda) throws IOException { 221 histogramFile = hisFile; 222 hisFileName = hisFile.toString(); 223 this.lambdaFileName = lamFileName; 224 225 biasCutoff = properties.getInt("lambda-bias-cutoff", DEFAULT_BIAS_CUTOFF); 226 biasMag = properties.getDouble("bias-gaussian-mag", DEFAULT_BIAS_MAG); 227 setDL(properties.getDouble("lambda-bin-width", DEFAULT_DL)); 228 dFL = properties.getDouble("flambda-bin-width", DEFAULT_DFL); 229 this.discreteLambda = discreteLambda; 230 // TODO: Eliminate the tempering flag. 231 tempering = properties.getBoolean("ost-alwaysTemper", DEFAULT_TEMPERING); 232 if (properties.containsKey("ost-temperOffset")) { 233 temperOffsetSet = true; 234 temperOffset = properties.getDouble("ost-temperOffset"); 235 } 236 237 if (histogramFile.exists()) { 238 try (HistogramReader hr = new HistogramReader(null, new BufferedReader(new FileReader(histogramFile)))) { 239 hr.readHistogramFile(); 240 temperature = hr.getTemperature(); 241 thetaMass = hr.getThetaMass(); 242 thetaFriction = hr.getThetaFriction(); 243 biasMag = hr.getBiasMag(); 244 biasCutoff = hr.getBiasCutoff(); 245 countInterval = hr.getCountInterval(); 246 setDL(hr.getLambdaBins()); 247 dFL = hr.getdUdLBinWidth(); 248 histogramRead = true; 249 } 250 } else { 251 histogramRead = false; 252 } 253 if (properties.containsKey("lambda-bias-cutoff")) { 254 lambdaBiasCutoff = properties.getInt("lambda-bias-cutoff"); 255 } else if (this.discreteLambda) { 256 lambdaBiasCutoff = 0; 257 } else { 258 lambdaBiasCutoff = biasCutoff; 259 } 260 } 261 262 // If setting a value can have side effects, or can be the side effect of another variable, 263 // set it by method rather than raw access. 264 265 /** 266 * Gets the Gaussian bias magnitude in kcal/mol. 267 * 268 * @return Gaussian bias magnitude in kcal/mol. 269 */ 270 public double getBiasMag() { 271 return biasMag; 272 } 273 274 /** 275 * Sets the bias magnitude, and if temperOffset has not previously been set by method or property, 276 * sets temperOffset to 20x that value. 277 * 278 * @param biasMag Gaussian bias magnitude in kcal/mol 279 */ 280 public void setBiasMag(double biasMag) { 281 this.biasMag = biasMag; 282 if (!temperOffsetSet) { 283 // TODO: Logger. 284 temperOffset = DEFAULT_BIAS_TO_OFFSET * biasMag; 285 } 286 } 287 288 public double getDL() { 289 continuousLambdaBins(); 290 return dL; 291 } 292 293 /** 294 * Sets dLambda; if an invalid value is provided (not 0-1), resets it to default 0.005. 295 * 296 * @param dLambda Lambda bin width. 297 */ 298 public void setDL(double dLambda) { 299 if (dLambda < 0.0 || dLambda > 1.0) { 300 dL = DEFAULT_DL; 301 } else { 302 dL = dLambda; 303 } 304 } 305 306 /** 307 * Sets dL based on a provided number of lambda bins. 308 * 309 * @param lambdaBins Number of lambda bins. 310 */ 311 public void setDL(int lambdaBins) { 312 assert lambdaBins > 2; 313 dL = 1.0 / (lambdaBins - 1); 314 } 315 316 public String getHisFileName() { 317 return hisFileName; 318 } 319 320 public File getHistogramFile() { 321 return histogramFile; 322 } 323 324 public String getLambdaFileName() { 325 return lambdaFileName; 326 } 327 328 public double getLambdaResetValue() { 329 return lambdaResetValue; 330 } 331 332 public void setLambdaResetValue(double lambdaResetValue) { 333 this.lambdaResetValue = lambdaResetValue; 334 // TODO: Should this be a (0-1] check instead of [0-1]? 335 resetStatistics = lambdaResetValue >= 0.0 && lambdaResetValue <= 1.0; 336 } 337 338 /** 339 * Gets the tempering offset. 340 * 341 * @return Gets the tempering offset in kcal/mol. 342 */ 343 public double getTemperOffset() { 344 return temperOffset; 345 } 346 347 /** 348 * Sets the tempering offset in kcal/mol. 349 * 350 * @param temperingOffset Tempering offset in kcal/mol. 351 */ 352 public void setTemperOffset(double temperingOffset) { 353 temperOffset = temperingOffset; 354 temperOffsetSet = true; 355 } 356 357 /** 358 * Returns the value of metaDynamics. 359 * 360 * @return metaDynamics. 361 */ 362 public boolean isMetaDynamics() { 363 return metaDynamics; 364 } 365 366 public void setMetaDynamics(boolean metaDynamics) { 367 this.metaDynamics = metaDynamics; 368 } 369 370 /** 371 * Returns the value of independentWalkers. 372 * 373 * @return independentWalkers. 374 */ 375 public boolean independentWalkers() { 376 return independentWalkers; 377 } 378 379 public boolean resetStatistics() { 380 return resetStatistics; 381 } 382 383 /** 384 * Sets the value of independentWalkers; if true, it also sets writeIndependent to true. 385 * 386 * @param iw Value to set independentWalkers to. 387 */ 388 public void setIndependentWalkers(boolean iw) { 389 independentWalkers = iw; 390 if (iw) { 391 setWriteIndependent(true); 392 } 393 } 394 395 /** 396 * Sets the value of writeIndependent. 397 * 398 * @param wi Value to set writeIndependent to. 399 * @throws IllegalArgumentException If wi is false and independentWalkers is true. 400 */ 401 public void setWriteIndependent(boolean wi) throws IllegalArgumentException { 402 if (!wi && independentWalkers) { 403 throw new IllegalArgumentException(" Independent walkers implies independent writing!"); 404 } 405 writeIndependent = wi; 406 } 407 408 /** 409 * Returns the value of writeIndependent. 410 * 411 * @return writeIndependent. 412 */ 413 public boolean writeIndependent() { 414 return writeIndependent; 415 } 416 417 /** 418 * If continuous lambda bins are in use, rectify dL to have an odd number of bins. If (1.0 / dL) 419 * would produce an odd number, dL is increased to make (1.0 / dL) the next even number. 420 */ 421 private void continuousLambdaBins() { 422 if (!discreteLambda) { 423 // This is not really "lambda bins" so much as it's just 1.0 / dL. 424 int fullWidthBins = (int) Math.round(1.0 / dL); 425 if (fullWidthBins % 2 == 0) { 426 ++fullWidthBins; 427 } 428 dL = 1.0 / (fullWidthBins - 1); 429 } 430 } 431 }