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-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 &gt; 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 }