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.algorithms.dynamics;
39  
40  import edu.rit.mp.DoubleBuf;
41  import edu.rit.pj.Comm;
42  import ffx.algorithms.AlgorithmListener;
43  import ffx.algorithms.Terminatable;
44  
45  import java.io.IOException;
46  import java.util.Random;
47  import java.util.logging.Level;
48  import java.util.logging.Logger;
49  
50  import static ffx.utilities.Constants.KCAL_TO_GRAM_ANG2_PER_PS2;
51  import static ffx.utilities.Constants.kB;
52  import static org.apache.commons.math3.util.FastMath.exp;
53  
54  /**
55   * The ReplicaExchange implements temperature and lambda replica exchange methods.
56   *
57   * @author Timothy D. Fenn and Michael J. Schnieders
58   * @since 1.0
59   */
60  public class ReplicaExchange implements Terminatable {
61  
62    private static final Logger logger = Logger.getLogger(ReplicaExchange.class.getName());
63    private final int nReplicas;
64    private final Random random;
65    /** Parallel Java world communicator. */
66    private final Comm world;
67    /** Rank of this process. */
68    private final int rank;
69    /**
70     * The parameters array stores communicated parameters for each process (i.e. each RepEx system).
71     * Currently, the array is of size [number of Processes][2].
72     */
73    private final double[][] parameters;
74    /**
75     * Each parameter array is wrapped inside a Parallel Java DoubleBuf for the All-Gather
76     * communication calls.
77     */
78    private final DoubleBuf[] parametersBuf;
79    private final MolecularDynamics replica;
80    private boolean done = false;
81    private boolean terminate = false;
82    private final double[] myParameters;
83    private final DoubleBuf myParametersBuf;
84    private final int[] temp2Rank;
85    private final int[] rank2Temp;
86    private double[] temperatures;
87    private final int[] tempAcceptedCount;
88    private final int[] rankAcceptedCount;
89    private final int[] tempTrialCount;
90    boolean monteCarlo;
91  
92    /**
93     * ReplicaExchange constructor.
94     *
95     * @param molecularDynamics The MolecularDynamics instance.
96     * @param listener A listener for algorithm events.
97     * @param temperature The temperature (K).
98     * @param exponent a double to set temperature ladder.
99     */
100   public ReplicaExchange(MolecularDynamics molecularDynamics, AlgorithmListener listener,
101       double temperature, double exponent, boolean monteCarlo) {
102 
103     this.replica = molecularDynamics;
104     this.monteCarlo = monteCarlo;
105 
106     // MolecularAssembly[] molecularAssemblies = molecularDynamics.getAssemblies();
107     // CompositeConfiguration properties = molecularAssemblies[0].getProperties()
108 
109     // Set up the Replica Exchange communication variables for Parallel Java communication between
110     // nodes.
111     world = Comm.world();
112 
113     // Number of processes is equal to the number of replicas.
114     int numProc = world.size();
115     rank = world.rank();
116 
117     nReplicas = numProc;
118     temperatures = new double[nReplicas];
119     temp2Rank = new int[nReplicas];
120     rank2Temp = new int[nReplicas];
121     tempAcceptedCount = new int[nReplicas];
122     rankAcceptedCount = new int[nReplicas];
123     tempTrialCount = new int[nReplicas];
124 
125     setExponentialTemperatureLadder(temperature, exponent);
126 
127     random = new Random();
128     random.setSeed(0);
129 
130     // Create arrays to store the parameters of all processes.
131     parameters = new double[nReplicas][2];
132     parametersBuf = new DoubleBuf[nReplicas];
133     for (int i = 0; i < nReplicas; i++) {
134       parametersBuf[i] = DoubleBuf.buffer(parameters[i]);
135     }
136 
137     // A convenience reference to the parameters of this process are updated
138     // during communication calls.
139     myParameters = parameters[rank];
140     myParametersBuf = parametersBuf[rank];
141   }
142 
143   /**
144    * Sample.
145    *
146    * @param cycles The number of cycles to sample.
147    * @param nSteps The number of steps per cycle.
148    * @param timeStep The time step (fsec).
149    * @param printInterval The interval (in steps) to print current status.
150    * @param saveInterval The interval (in steps) to save a snapshot.
151    */
152   public void sample(int cycles, long nSteps, double timeStep, double printInterval,
153       double saveInterval) {
154     done = false;
155     terminate = false;
156     for (int i = 0; i < cycles; i++) {
157       // Check for termination request.
158       if (terminate) {
159         done = true;
160         break;
161       }
162       dynamic(nSteps, timeStep, printInterval, saveInterval);
163       logger.info(String.format(" Applying exchange condition for cycle %d.", i));
164       exchange(i);
165     }
166   }
167 
168   /**
169    * setExponentialTemperatureLadder.
170    *
171    * @param lowTemperature a double.
172    * @param exponent a double.
173    */
174   public void setExponentialTemperatureLadder(double lowTemperature, double exponent) {
175     for (int i = 0; i < nReplicas; i++) {
176       temperatures[i] = lowTemperature * exp(exponent * i);
177       temp2Rank[i] = i;
178       rank2Temp[i] = i;
179     }
180   }
181 
182   /**
183    * Setter for the field <code>temperatures</code>.
184    *
185    * @param temperatures The temperatures for each replica.
186    */
187   public void setTemperatures(double[] temperatures) {
188     assert (temperatures.length == nReplicas);
189     this.temperatures = temperatures;
190   }
191 
192   /**
193    * {@inheritDoc}
194    *
195    * <p>This should be implemented as a blocking interrupt; when the method returns the <code>
196    * Terminatable</code> algorithm has reached a clean termination point. For example, between
197    * minimize or molecular dynamics steps.
198    */
199   @Override
200   public void terminate() {
201     terminate = true;
202     while (!done) {
203       synchronized (this) {
204         try {
205           wait(1);
206         } catch (InterruptedException e) {
207           logger.log(Level.WARNING, "Exception terminating replica exchange.\n", e);
208         }
209       }
210     }
211   }
212 
213   /**
214    * All processes complete the exchanges identically given the same Random number seed.
215    *
216    * @param cycle The current cycle.
217    */
218   private void exchange(int cycle) {
219     int start;
220     int increment;
221     if (monteCarlo) {
222       // 1 M.C. trial per temperature (except for those at the ends of the ladder).
223       start = cycle % 2;
224       increment = 2;
225     } else {
226       // 2 M.C. trials per temperature (except for those at the ends of the ladder).
227       start = 0;
228       increment = 1;
229     }
230     // Loop over temperatures
231     for (int temperature = start; temperature < nReplicas - 1; temperature += increment) {
232 
233       // Ranks for temperatures A and B
234       int rankA = temp2Rank[temperature];
235       int rankB = temp2Rank[temperature + 1];
236 
237       // Load temperature, beta and energy for each rank.
238       double tempA = parameters[rankA][0];
239       double tempB = parameters[rankB][0];
240       double betaA = KCAL_TO_GRAM_ANG2_PER_PS2 / (tempA * kB);
241       double betaB = KCAL_TO_GRAM_ANG2_PER_PS2 / (tempB * kB);
242       double energyA = parameters[rankA][1];
243       double energyB = parameters[rankB][1];
244 
245       // Compute the change in energy over kT (E/kT) for the Metropolis criteria.
246       double deltaE = (energyA - energyB) * (betaB - betaA);
247       // If the Metropolis criteria is satisfied, do the switch.
248 
249       //Count the number of trials for each temp
250       tempTrialCount[temperature]++;
251       if (deltaE < 0.0 || random.nextDouble() < exp(-deltaE)) {
252         tempAcceptedCount[temperature]++;
253         double tempAcceptance =
254             tempAcceptedCount[temperature] * 100.0 / (tempTrialCount[temperature]);
255 
256         double rankAcceptance;
257         if (tempA < tempB) {
258           rankAcceptedCount[rankA]++;
259           rankAcceptance = rankAcceptedCount[rankA] * 100.0 / (tempTrialCount[temperature]);
260         } else {
261           rankAcceptedCount[rankB]++;
262           rankAcceptance = rankAcceptedCount[rankB] * 100.0 / (tempTrialCount[temperature + 1]);
263         }
264 
265         // Swap temperature and energy values.
266         parameters[rankA][0] = tempB;
267         parameters[rankB][0] = tempA;
268 
269         parameters[rankA][1] = energyB;
270         parameters[rankB][1] = energyA;
271 
272         // Map temperatures to process ranks.
273         temp2Rank[temperature] = rankB;
274         temp2Rank[temperature + 1] = rankA;
275 
276         // Map ranks to temperatures.
277         rank2Temp[rankA] = temperature + 1;
278         rank2Temp[rankB] = temperature;
279 
280         logger.info(String.format(
281             " RepEx accepted (%5.1f%%) (%5.1f%%) for %6.2f (%d) and %6.2f (%d) for dE=%10.4f.",
282             tempAcceptance, rankAcceptance, tempA, rankA, tempB, rankB, deltaE));
283       } else {
284         double tempAcceptance =
285             tempAcceptedCount[temperature] * 100.0 / (tempTrialCount[temperature]);
286         double rankAcceptance =
287             rankAcceptedCount[temperature] * 100.0 / (tempTrialCount[temperature]);
288         logger.info(String.format(
289             " RepEx rejected (%5.1f%%) (f%5.1f%%) for %6.2f (%d) and %6.2f (%d) for dE=%10.4f.",
290             tempAcceptance, rankAcceptance, tempA, rankA, tempB, rankB, deltaE));
291       }
292     }
293   }
294 
295   /**
296    * Blocking dynamic steps: when this method returns each replica has completed the requested number
297    * of steps.
298    *
299    * @param nSteps the number of time steps.
300    * @param timeStep the time step (fsec).
301    * @param printInterval the number of steps between logging updates.
302    * @param saveInterval the number of steps between saving snapshots.
303    */
304   private void dynamic(final long nSteps, final double timeStep, final double printInterval,
305       final double saveInterval) {
306 
307     int i = rank2Temp[rank];
308 
309     // Start this processes MolecularDynamics instance sampling.
310     boolean initVelocities = true;
311     replica.dynamic(nSteps, timeStep, printInterval, saveInterval, temperatures[i], initVelocities,
312         null);
313 
314     // Update this ranks' parameter array to be consistent with the dynamics.
315     myParameters[0] = temperatures[i];
316     myParameters[1] = replica.state.getPotentialEnergy();
317 
318     // Gather all parameters from the other processes.
319     try {
320       world.allGather(myParametersBuf, parametersBuf);
321     } catch (IOException ex) {
322       String message = " Replica Exchange allGather failed.";
323       logger.log(Level.SEVERE, message, ex);
324     }
325   }
326 }