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-2025.
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 java.util.Random;
41  import java.util.logging.Level;
42  import java.util.logging.Logger;
43  import java.util.stream.IntStream;
44  
45  import static ffx.numerics.estimator.EstimateBootstrapper.getBootstrapIndices;
46  import static ffx.utilities.Constants.R;
47  import static java.lang.Double.isInfinite;
48  import static java.lang.Double.isNaN;
49  import static java.lang.String.format;
50  import static java.util.Arrays.copyOf;
51  import static org.apache.commons.math3.util.FastMath.exp;
52  import static org.apache.commons.math3.util.FastMath.log;
53  
54  /**
55   * The Zwanzig class implements exponential averaging/free energy perturbation using the Zwanzig
56   * relationship, in either the forwards or backwards direction (not both).
57   *
58   * @author Michael J. Schnieders
59   * @author Jacob M. Litman
60   * @since 1.0
61   */
62  public class Zwanzig extends SequentialEstimator implements BootstrappableEstimator {
63  
64    private static final Logger logger = Logger.getLogger(Zwanzig.class.getName());
65  
66    /**
67     * Directionality of the Zwanzig estimation (forwards perturbation or backwards perturbation).
68     */
69    public final Directionality directionality;
70    /**
71     * Whether the Zwanzig estimation is forwards or backwards.
72     */
73    private final boolean forwards;
74    /**
75     * Number of windows.
76     */
77    private final int nWindows;
78    /**
79     * Free energy difference for each window.
80     */
81    private final double[] freeEnergyDifferences;
82    /**
83     * Enthalpy difference for each window.
84     */
85    private final double[] enthalpyDifferences;
86    /**
87     * Free energy difference uncertainty for each window.
88     */
89    private final double[] freeEnergyDifferenceUncertainties;
90    /**
91     * Random number generator for bootstrapping.
92     */
93    private final Random random;
94    /**
95     * Total free energy difference as a sum over windows.
96     */
97    private double totalFreeEnergyDifference;
98    /**
99     * Total free energy difference uncertainty:
100    * totalFreeEnergyUncertainty = Sqrt [ Sum over Windows [ Window Variance ] ]
101    */
102   private double totalFreeEnergyDifferenceUncertainty;
103   /**
104    * The total enthalpy difference.
105    */
106   private double totalEnthalpyDifference;
107 
108   /**
109    * Estimates a free energy using the Zwanzig relationship. The temperature array can be of length 1
110    * if all elements are meant to be the same temperature.
111    *
112    * <p>The first dimension of the energies arrays corresponds to the lambda values/windows. The
113    * second dimension (can be of uneven length) corresponds to potential energies of snapshots
114    * sampled from that lambda value, calculated either at that lambda value, the lambda value below,
115    * or the lambda value above. The arrays eLambdaMinusdL[0] and eLambdaPlusdL[n-1] is expected to be all
116    * NaN.
117    *
118    * @param lambdaValues   Lambda values for the samples.
119    * @param eLambdaMinusdL Potential energies of state L at lambda L-dL. Ignored for forwards FEP.
120    * @param eLambda        Potential energies of state L at lambda L.
121    * @param eLambdaPlusdL  Potential energies of state L at lambda L+dL. Ignored for backwards FEP.
122    * @param temperature    Temperature each lambda window was run at (single-element indicates identical temperatures).
123    * @param directionality Forwards vs. backwards FEP.
124    */
125   public Zwanzig(double[] lambdaValues, double[][] eLambdaMinusdL, double[][] eLambda,
126                  double[][] eLambdaPlusdL, double[] temperature, Directionality directionality) {
127     super(lambdaValues, eLambdaMinusdL, eLambda, eLambdaPlusdL, temperature);
128     this.directionality = directionality;
129     nWindows = nStates - 1;
130 
131     freeEnergyDifferences = new double[nWindows];
132     freeEnergyDifferenceUncertainties = new double[nWindows];
133     enthalpyDifferences = new double[nWindows];
134 
135     forwards = directionality.equals(Directionality.FORWARDS);
136     random = new Random();
137 
138     estimateDG();
139   }
140 
141   /**
142    * {@inheritDoc}
143    */
144   @Override
145   public Zwanzig copyEstimator() {
146     return new Zwanzig(lamValues, eLambdaMinusdL, eLambda, eLambdaPlusdL, temperatures, directionality);
147   }
148 
149   /**
150    * {@inheritDoc}
151    */
152   @Override
153   public final void estimateDG(final boolean randomSamples) {
154     double cumDG = 0;
155 
156     Level warningLevel = randomSamples ? Level.FINE : Level.WARNING;
157 
158     for (int i = 0; i < nWindows; i++) {
159       final int windowIndex = i + (forwards ? 0 : 1);
160       final double[] referenceEnergy = eLambda[windowIndex];
161       final double[] perturbedEnergy = forwards ? eLambdaPlusdL[windowIndex] : eLambdaMinusdL[windowIndex];
162       final double sign = forwards ? 1.0 : -1.0;
163       final double kT = temperatures[windowIndex] * R;
164       final double beta = 1.0 / kT;
165       final int numSamples = referenceEnergy.length;
166       if (numSamples == 0) {
167         logger.log(warningLevel, " Skipping frame " + i + " due to lack of snapshots!");
168         continue;
169       }
170 
171       // With no iteration-to-convergence, generating a fresh random index is OK.
172       int[] sampleIndices = randomSamples ? getBootstrapIndices(numSamples, random) : IntStream.range(0, numSamples).toArray();
173 
174       double boltzmannFactorSum = 0.0;
175       double weightedMeanEnergy = 0.0;
176       double meanEnergy = 0.0;
177       for (int j = 0; j < numSamples; j++) {
178         int index = sampleIndices[j];
179         final double dE = perturbedEnergy[index] - referenceEnergy[index];
180         final double weight = exp(-beta * dE);
181         boltzmannFactorSum += weight;
182         if (forwards) {
183           meanEnergy += referenceEnergy[index];
184           weightedMeanEnergy += perturbedEnergy[index] * weight;
185         } else {
186           meanEnergy += perturbedEnergy[index];
187           weightedMeanEnergy += referenceEnergy[index] * weight;
188         }
189       }
190       meanEnergy = meanEnergy / numSamples;
191       double dG = -sign * kT * log(boltzmannFactorSum / numSamples);
192 
193       if (isNaN(dG) || isInfinite(dG)) {
194         logger.severe(format(" Change in free energy (%9.4f) for window (%2d of %2d) failed. " +
195                 "Sign: %9.4f, Beta: %9.4f, Temp: %9.4f, Sum: %9.4f, Len: %3d, Log: %9.4f",
196             dG, i, nWindows, sign, kT, temperatures[windowIndex], boltzmannFactorSum, numSamples, log(boltzmannFactorSum / numSamples)));
197       }
198 
199       freeEnergyDifferences[i] = dG;
200       enthalpyDifferences[i] = sign * ((weightedMeanEnergy / boltzmannFactorSum) - meanEnergy);
201       freeEnergyDifferenceUncertainties[i] = 0.0;
202       totalEnthalpyDifference += enthalpyDifferences[i];
203       cumDG += dG;
204     }
205 
206     totalFreeEnergyDifference = cumDG;
207     totalFreeEnergyDifferenceUncertainty = 0.0;
208   }
209 
210   /**
211    * {@inheritDoc}
212    */
213   @Override
214   public final void estimateDG() {
215     estimateDG(false);
216   }
217 
218   /**
219    * {@inheritDoc}
220    */
221   @Override
222   public double getTotalFreeEnergyDifference() {
223     return totalFreeEnergyDifference;
224   }
225 
226   /**
227    * {@inheritDoc}
228    */
229   @Override
230   public double[] getFreeEnergyDifferences() {
231     return copyOf(freeEnergyDifferences, nWindows);
232   }
233 
234   /**
235    * {@inheritDoc}
236    */
237   @Override
238   public double getTotalFEDifferenceUncertainty() {
239     return totalFreeEnergyDifferenceUncertainty;
240   }
241 
242   /**
243    * {@inheritDoc}
244    */
245   @Override
246   public double[] getFEDifferenceUncertainties() {
247     return copyOf(freeEnergyDifferenceUncertainties, nWindows);
248   }
249 
250   /**
251    * {@inheritDoc}
252    */
253   @Override
254   public int getNumberOfBins() {
255     return nWindows;
256   }
257 
258   /**
259    * {@inheritDoc}
260    */
261   @Override
262   public double getTotalEnthalpyDifference() {
263     return totalEnthalpyDifference;
264   }
265 
266   /**
267    * {@inheritDoc}
268    */
269   @Override
270   public double[] getEnthalpyDifferences() {
271     return copyOf(enthalpyDifferences, nWindows);
272   }
273 
274   /**
275    * Directionality of the Zwanzig estimation (forwards perturbation or backwards perturbation).
276    */
277   public enum Directionality {
278     /**
279      * Forwards perturbation.
280      */
281     FORWARDS,
282     /**
283      * Backwards perturbation.
284      */
285     BACKWARDS
286   }
287 }