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