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 }