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-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 java.util.ArrayList;
41  
42  import static java.lang.System.arraycopy;
43  import static java.util.Arrays.copyOf;
44  import static java.util.Arrays.fill;
45  import static java.util.Arrays.stream;
46  
47  /**
48   * The SequentialEstimator abstract class defines a statistical estimator based on perturbative
49   * potential energy differences between adjacent windows (e.g. exponential free energy perturbation,
50   * Bennett Acceptance Ratio, etc).
51   *
52   * @author Michael J. Schnieders
53   * @author Jacob M. Litman
54   * @since 1.0
55   */
56  public abstract class SequentialEstimator implements StatisticalEstimator {
57  
58    protected final double[] lamValues;
59    protected final double[][] eLow;
60    protected final double[][] eAt;
61    protected final double[][] eHigh;
62    protected final double[][][] eAll; // [[[energies]perturbationsAcrossLambda]lambdaWindow] N X K array for each lambda window
63    protected final double[][] eAllFlat; // [lambda][evaluationsAtLambda]
64    protected final double[] temperatures;
65    protected final int nTrajectories;
66  
67    /**
68     * The SequentialEstimator constructor largely just copies its parameters into local variables.
69     * Most arrays are duplicated (rather a just copying their reference).
70     * The temperature array can be of length 1 if all elements are meant to be the same temperature.
71     *
72     * <p>The first dimension of the energies arrays corresponds to the lambda values/windows. The
73     * second dimension (can be of uneven length) corresponds to potential energies of snapshots
74     * sampled from that lambda value, calculated either at that lambda value, the lambda value below,
75     * or the lambda value above. The arrays energiesLow[0] and energiesHigh[n-1] is expected to be all
76     * NaN.
77     *
78     * @param lambdaValues Values of lambda dynamics was run at.
79     * @param energiesLow Potential energies of trajectory L at lambda L-dL.
80     * @param energiesAt Potential energies of trajectory L at lambda L.
81     * @param energiesHigh Potential energies of trajectory L at lambda L+dL.
82     * @param temperature Temperature each lambda window was run at (single-element indicates
83     *     identical temperatures).
84     */
85    public SequentialEstimator(double[] lambdaValues, double[][] energiesLow, double[][] energiesAt,
86        double[][] energiesHigh, double[] temperature) {
87      nTrajectories = lambdaValues.length;
88      eAll = null;
89      eAllFlat = null;
90  
91      assert stream(energiesLow[0]).allMatch(Double::isNaN)
92          && stream(energiesHigh[nTrajectories - 1]).allMatch(Double::isNaN);
93  
94      assert nTrajectories == energiesAt.length
95          && nTrajectories == energiesLow.length
96          && nTrajectories == energiesHigh.length
97          : "One of the energy arrays is of the incorrect length in the first dimension!";
98  
99      this.lamValues = copyOf(lambdaValues, nTrajectories);
100     temperatures = new double[nTrajectories];
101     if (temperature.length == 1) {
102       fill(temperatures, temperature[0]);
103     } else {
104       arraycopy(temperature, 0, temperatures, 0, nTrajectories);
105     }
106 
107     // Just in case, copy the arrays rather than storing them as provided.
108     eLow = new double[nTrajectories][];
109     eAt = new double[nTrajectories][];
110     eHigh = new double[nTrajectories][];
111     for (int i = 0; i < nTrajectories; i++) {
112       eLow[i] = copyOf(energiesLow[i], energiesLow[i].length);
113       eAt[i] = copyOf(energiesAt[i], energiesAt[i].length);
114       eHigh[i] = copyOf(energiesHigh[i], energiesHigh[i].length);
115     }
116   }
117 
118 
119   /**
120    * The SequentialEstimator constructor largely just copies its parameters into local variables.
121    * Most arrays are duplicated (rather a just copying their reference).
122    * The temperature array can be of length 1 if all elements are meant to be the same temperature.
123    *<p>
124    * This constructor is meant for lower variance estimators such as MBAR & WHAM. These methods require energy
125    * evaluations from all lambda windows at all lambda values. The energiesAll array is expected to be
126    * of the form energiesAll[lambdaWindow][windowPerspective][lambdaWindowSnapshotPerspectiveEnergy].
127    * As an example, at the 3rd lambda window, the energiesAll[2] array should contain the energies
128    * of all the snapshots from the 3rd lambda window evaluated at all lambda values. energiesAll[2][3] is a
129    * list of all snapshots from lambda 3 evaluated with the potential of lambda 4. energiesAll[2][3][4] is
130    * the 5th snapshot from lambda 3 evaluated with the potential of lambda 4.
131    * <p>
132    * This constructor also breaks energiesAll into a flattened array (across the second dimension) such that
133    * the first dimension is the lambda window where the energy was evaluated and the second dimension is the
134    * samples. energiesAll is also broken down into eAt, eLow, and eHigh arrays for convenience & so that BAR
135    * calculations can be performed and compared.
136    *
137    * @param lambdaValues Values of lambda dynamics was run at.
138    * @param energiesAll Potential energies of trajectory L at all other lambdas.
139    * @param temperature Temperature each lambda window was run at (single-element indicates
140    *                    identical temperatures).
141    */
142   public SequentialEstimator(double[] lambdaValues, double[][][] energiesAll, double[] temperature) {
143     nTrajectories = lambdaValues.length;
144 
145     assert nTrajectories == energiesAll.length
146         : "The energy arrays is of the incorrect length in the first lambda dimension!";
147 
148     assert nTrajectories == energiesAll[0].length
149         : "The energy arrays is of the incorrect length in the second lambda dimension!";
150 
151     this.lamValues = copyOf(lambdaValues, nTrajectories);
152     temperatures = new double[nTrajectories];
153     if (temperature.length == 1) {
154       fill(temperatures, temperature[0]);
155     } else {
156       arraycopy(temperature, 0, temperatures, 0, nTrajectories);
157     }
158 
159     // Just in case, deep copy the array rather than storing them as provided.
160     eAll = new double[nTrajectories][][];
161     for (int i = 0; i < nTrajectories; i++) {
162       eAll[i] = new double[energiesAll[i].length][];
163       for (int j = 0; j < energiesAll[i].length; j++) {
164         eAll[i][j] = copyOf(energiesAll[i][j], energiesAll[i][j].length);
165       }
166     }
167 
168     eAllFlat = new double[nTrajectories][];
169     for (int i = 0; i < nTrajectories; i++) {
170         ArrayList<Double> temp = new ArrayList<>();
171         for(int j = 0; j < nTrajectories; j++) {
172           for(int k = 0; k < eAll[j][i].length; k++) {
173             temp.add(eAll[j][i][k]);
174           }
175         }
176         eAllFlat[i] = temp.stream().mapToDouble(Double::doubleValue).toArray();
177     }
178 
179     // Assert that lengths of the energiesAll arrays are correct.
180     for(int i = 0; i < nTrajectories; i++){
181       assert eAll[i].length == nTrajectories :
182               "The energy arrays is of the incorrect length in the second lambda dimension at lambda " + i + "!";
183       int nSnapshots = eAll[i][0].length;
184         for(int j = 0; j < nTrajectories; j++){
185             assert eAll[i][j].length == nSnapshots :
186                     "The energy arrays is of the incorrect length in numSnaps dimension at lambda " +
187                             i + " for evaluation at lambda " + j + "!";
188         }
189     }
190 
191     // Initialize the eLow, eAt, and eHigh arrays to their expected values from eAll.
192     eLow = new double[nTrajectories][eAll[0][0].length];
193     fill(eLow[0], Double.NaN);
194     eAt = new double[nTrajectories][];
195     eHigh = new double[nTrajectories][eAll[0][0].length];
196     fill(eHigh[nTrajectories - 1], Double.NaN);
197     for (int i = 0; i < nTrajectories; i++) {
198       if (i != 0) {
199         eLow[i] = copyOf(eAll[i][i-1], eAll[i][i-1].length);
200       }
201       eAt[i] = copyOf(eAll[i][i], eAll[i][i].length);
202       if(i != nTrajectories - 1) {
203         eHigh[i] = copyOf(eAll[i][i + 1], eAll[i][i + 1].length);
204       }
205     }
206   }
207 }