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.algorithms.dynamics; 39 40 import ffx.numerics.math.RunningStatistics; 41 42 import java.util.logging.Logger; 43 44 import static java.lang.String.format; 45 46 /** 47 * NonEquilbriumDynamics is a class that contains methods to control 48 * non-equilibrium molecular dynamics simulations. 49 */ 50 public class NonEquilbriumDynamics { 51 52 private static final Logger logger = Logger.getLogger(NonEquilbriumDynamics.class.getName()); 53 54 /** 55 * Begin at L=1 and decrease to L=0. 56 */ 57 private final boolean reverseNEQ; 58 /** 59 * The number of non-equilibrium lambda steps. 60 */ 61 private final int nonEquilibriumLambdaSteps; 62 /** 63 * The non-equilibrium work values. 64 */ 65 private final RunningStatistics nonEquilibriumWorkValues; 66 /** 67 * The total number of MD steps. 68 */ 69 private long totalMDSteps = 1; 70 /** 71 * The non-equilibrium lambda update frequency. 72 * <p> 73 * The number of MD steps between updates of the non-equilibrium lambda value, which is a function of 74 * the total number of MD times steps and the number of non-equilibrium lambda steps. 75 */ 76 private long nonEquilibiumLambdaUpdateFrequency = Long.MAX_VALUE; 77 78 /** 79 * Constructor for NonEquilbriumDynamics. 80 * 81 * @param nonEquilibriumLambdaSteps The number of non-equilibrium lambda steps. 82 * @param reverseNEQ If true, lambda values should decrease from 1 to 0. 83 */ 84 public NonEquilbriumDynamics(int nonEquilibriumLambdaSteps, boolean reverseNEQ) { 85 if (nonEquilibriumLambdaSteps < 1) { 86 this.nonEquilibriumLambdaSteps = 100; 87 } else { 88 this.nonEquilibriumLambdaSteps = nonEquilibriumLambdaSteps; 89 } 90 this.reverseNEQ = reverseNEQ; 91 nonEquilibriumWorkValues = new RunningStatistics(); 92 } 93 94 /** 95 * Get the number of non-equilibrium lambda steps. 96 * 97 * @return The number of non-equilibrium lambda steps. 98 */ 99 public int getNonEquilibriumLambdaSteps() { 100 return nonEquilibriumLambdaSteps; 101 } 102 103 /** 104 * Get the initial lambda value. 105 * @return The initial lambda value. 106 */ 107 public double getInitialLambda() { 108 return reverseNEQ ? 1.0 : 0.0; 109 } 110 111 /** 112 * Configure increments of the non-equilibrium lambda values based on the total number of MD steps. 113 * 114 * @param nSteps The total number of MD steps. 115 * @return The total number of MD steps may be adjusted to be a multiple of the non-equilibrium lambda steps. 116 */ 117 public long setMDSteps(long nSteps) { 118 if (nSteps < 1) { 119 long defaultSteps = 100L * nonEquilibriumLambdaSteps; 120 logger.info(format(" Invalid number of MD steps %d. Setting the number of steps to %d.", nSteps, defaultSteps)); 121 nSteps = defaultSteps; 122 } 123 124 if (nSteps % nonEquilibriumLambdaSteps != 0) { 125 logger.info(format(" Non-equilibrium lambda steps (%d) is not a multiple of total steps (%d).", 126 nonEquilibriumLambdaSteps, nSteps)); 127 nSteps = nSteps - (nSteps % nonEquilibriumLambdaSteps); 128 logger.info(format(" Number of steps adjusted to %d.", nSteps)); 129 } 130 nonEquilibiumLambdaUpdateFrequency = nSteps / nonEquilibriumLambdaSteps; 131 totalMDSteps = nSteps; 132 return nSteps; 133 } 134 135 /** 136 * Check if the non-equilibrium lambda value should be updated at a given MD step. 137 * 138 * @param step The MD step number. 139 * @return True if the non-equilibrium lambda value should be updated. 140 */ 141 public boolean isUpdateStep(long step) { 142 if (step < 1 || step > totalMDSteps) { 143 logger.severe(format(" Invalid MD step number %d. Must be between 1 and %d.", step, totalMDSteps)); 144 return false; 145 } 146 // The last step is a special case. 147 if (step == totalMDSteps) { 148 return true; 149 } 150 return (step - 1) % nonEquilibiumLambdaUpdateFrequency == 0; 151 } 152 153 /** 154 * Add a work contribution. 155 * 156 * @param work The work value. 157 */ 158 public void addWork(double work) { 159 nonEquilibriumWorkValues.addValue(work); 160 } 161 162 /** 163 * Get the total work for a given range of lambda bins. 164 * 165 * @return The total work. 166 */ 167 public double getWork() { 168 return nonEquilibriumWorkValues.getSum(); 169 } 170 171 /** 172 * Get the non-equilibrium lambda value for a given MD step. 173 * 174 * @param step The MD step number. 175 * @param currentLambda The current lambda value. 176 * @return The lambda value. 177 */ 178 public double getNextLambda(long step, double currentLambda) { 179 if (isUpdateStep(step)) { 180 int lambdaBin = getCurrentLambdaBin(step); 181 double lambdaStepSize = 1.0 / nonEquilibriumLambdaSteps; 182 if (reverseNEQ) { 183 return 1.0 - lambdaBin * lambdaStepSize; 184 } else { 185 return lambdaBin * lambdaStepSize; 186 } 187 } else { 188 logger.warning(format(" Non-equilibrium lambda update frequency is %d, but step %d is not a multiple of this frequency.", 189 nonEquilibiumLambdaUpdateFrequency, step - 1)); 190 logger.warning(format(" Returning the current lambda value %6.4f.", currentLambda)); 191 return currentLambda; 192 } 193 } 194 195 /** 196 * Get the current lambda bin for a given MD step. 197 * 198 * @param step The MD step number. 199 * @return The lambda bin. 200 */ 201 public int getCurrentLambdaBin(long step) { 202 if (step == totalMDSteps) { 203 return nonEquilibriumLambdaSteps; 204 } else if (isUpdateStep(step)) { 205 return (int) ((step - 1) / nonEquilibiumLambdaUpdateFrequency); 206 } else { 207 logger.warning(format(" Non-equilibrium lambda update frequency is %d, but step %d is not a multiple of this frequency.", 208 nonEquilibiumLambdaUpdateFrequency, step - 1)); 209 return 0; 210 } 211 } 212 213 }