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 }