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.optimize.anneal;
39  
40  import ffx.algorithms.AlgorithmListener;
41  import ffx.algorithms.Terminatable;
42  import ffx.algorithms.dynamics.MolecularDynamics;
43  import ffx.algorithms.dynamics.integrators.IntegratorEnum;
44  import ffx.algorithms.dynamics.thermostats.ThermostatEnum;
45  import ffx.numerics.Potential;
46  import ffx.potential.MolecularAssembly;
47  import java.io.File;
48  import java.util.Set;
49  import java.util.logging.Level;
50  import java.util.logging.Logger;
51  
52  /**
53   * Run NVT molecular dynamics at a series of temperatures to optimize a structure.
54   *
55   * @author Michael J. Schnieders
56   * @author Jacob M. Litman
57   * @since 1.0
58   */
59  public class SimulatedAnnealing implements Runnable, Terminatable {
60  
61    private static final Logger logger = Logger.getLogger(SimulatedAnnealing.class.getName());
62    /** The MolecularDynamics instance used for Simulated Annealing. */
63    private final MolecularDynamics molecularDynamics;
64    /** Schedule for annealing. */
65    private final AnnealingSchedule schedule;
66    /** Number of MD steps per annealing window. */
67    private final long mdSteps;
68    /** Integration time step. */
69    private final double timeStep;
70    /** Whether to reinitialize velocities at the start of each time step. */
71    private final boolean reInitVelocity;
72    /** Number of MD steps per OpenMM cycle (assuming OpenMM is used!). */
73    private final int trajSteps = 1;
74    /** Interval to print updates to the screen. */
75    private double printInterval = 0.01;
76    /** Flag to indicate the algorithm is done. */
77    private boolean done = true;
78    /** Flag to indicate the UI has requested the algorithm terminate. */
79    private boolean terminate;
80  
81    private double saveFrequency = 0.1;
82    /** Restart file. */
83    private File dynFile;
84  
85    /**
86     * Constructor for SimulatedAnnealing.
87     *
88     * @param molecularAssembly The Molecular Assembly to operate on.
89     * @param potentialEnergy The potential to anneal against.
90     * @param algorithmListener The algorithm listener is a callback to UI.
91     * @param requestedThermostat The requested thermostat.
92     * @param requestedIntegrator The requested integrator.
93     * @param annealingSchedule Schedule of temperatures to simulate at.
94     * @param mdSteps Steps per SA window.
95     * @param timeStep Time step for MD in psec.
96     * @param reInitVelocity Request velocities to be reinitialized.
97     * @param dynFile Dynamics restart file to begin from.
98     */
99    public SimulatedAnnealing(MolecularAssembly molecularAssembly, Potential potentialEnergy,
100       AlgorithmListener algorithmListener, ThermostatEnum requestedThermostat,
101       IntegratorEnum requestedIntegrator, AnnealingSchedule annealingSchedule, long mdSteps,
102       double timeStep, boolean reInitVelocity, File dynFile) {
103 
104     molecularDynamics = MolecularDynamics.dynamicsFactory(molecularAssembly, potentialEnergy,
105         algorithmListener, requestedThermostat, requestedIntegrator);
106     this.schedule = annealingSchedule;
107     this.mdSteps = mdSteps;
108     this.timeStep = timeStep;
109     this.reInitVelocity = reInitVelocity;
110     this.dynFile = dynFile;
111   }
112 
113   /** anneal */
114   public void anneal() {
115     // Return if already running; Could happen if two threads call anneal
116     // on the same SimulatedAnnealing instance.
117     if (!done) {
118       logger.warning(" Programming error - a thread invoked anneal when it was already running.");
119       return;
120     }
121     done = false;
122     logger.info(" Beginning simulated annealing");
123     begin();
124   }
125 
126   /**
127    * getKineticEnergy.
128    *
129    * @return a double.
130    */
131   public double getKineticEnergy() {
132     return molecularDynamics.getKineticEnergy();
133   }
134 
135   /**
136    * getPotentialEnergy.
137    *
138    * @return a double.
139    */
140   public double getPotentialEnergy() {
141     return molecularDynamics.getPotentialEnergy();
142   }
143 
144   /**
145    * getTemperature.
146    *
147    * @return a double.
148    */
149   public double getTemperature() {
150     return molecularDynamics.getTemperature();
151   }
152 
153   /**
154    * getTotalEnergy.
155    *
156    * @return a double.
157    */
158   public double getTotalEnergy() {
159     return molecularDynamics.getTotalEnergy();
160   }
161 
162   /**
163    * {@inheritDoc}
164    *
165    * <p>This method should only be invoked within the SimulatedAnnealing instance.
166    */
167   @Override
168   public void run() {
169     done = false;
170     terminate = false;
171 
172     int minMdSteps = (int) (mdSteps * schedule.minWindowLength());
173     if (minMdSteps < trajSteps) {
174       logger.warning(String.format(
175           " Minimum number of MD steps per annealing cycle %d was less than steps per OpenMM MD cycle %d! Setting steps per MD cycle to %d",
176           minMdSteps, trajSteps, minMdSteps));
177       setTrajectorySteps(minMdSteps);
178     }
179 
180     int nWindows = schedule.getNumWindows();
181     boolean forceFirstReinit = (dynFile == null);
182 
183     for (int i = 0; i < nWindows; i++) {
184       double temperature = schedule.getTemperature(i);
185       int nSteps = (int) (schedule.windowLength(i) * mdSteps);
186       logger.info(
187           String.format(" Annealing window %d: %d steps at %9.4g K", (i + 1), nSteps, temperature));
188       molecularDynamics.dynamic(nSteps, timeStep, printInterval, saveFrequency, temperature,
189           (reInitVelocity || forceFirstReinit), dynFile);
190       if (dynFile == null) {
191         dynFile = molecularDynamics.getDynFile();
192       }
193       forceFirstReinit = false;
194       if (terminate) {
195         logger.info(String.format("\n Terminating at temperature %8.3f.\n", temperature));
196         break;
197       }
198     }
199     if (!terminate) {
200       logger.info(String.format(" Completed %8d annealing steps\n", nWindows));
201     }
202 
203     done = true;
204     terminate = false;
205   }
206 
207   /**
208    * Setter for the field <code>printInterval</code>.
209    *
210    * @param printInterval a double.
211    */
212   public void setPrintInterval(double printInterval) {
213     this.printInterval = printInterval;
214   }
215 
216   /**
217    * Method to set the Restart Frequency.
218    *
219    * @param restart the time between writing restart files.
220    * @throws java.lang.IllegalArgumentException If restart frequency is not a positive number
221    */
222   public void setRestartFrequency(double restart) throws IllegalArgumentException {
223     if (Double.isFinite(restart) && restart > 0) {
224       molecularDynamics.setRestartFrequency(restart);
225     } else {
226       throw new IllegalArgumentException(
227           String.format(" Restart frequency must be positive finite, was %10.4g", restart));
228     }
229   }
230 
231   /**
232    * Sets the frequency of writing to the trajectory file.
233    *
234    * @param save Frequency (psec^-1) to write out the trajectory.
235    */
236   public void setSaveFrequency(double save) {
237     this.saveFrequency = save;
238   }
239 
240   /**
241    * Sets the number of steps to use per OpenMM cycle.
242    *
243    * @param trajectorySteps Steps per OpenMM cycle.
244    */
245   public void setTrajectorySteps(int trajectorySteps) {
246     molecularDynamics.setIntervalSteps(trajectorySteps);
247   }
248 
249   /** {@inheritDoc} */
250   @Override
251   public void terminate() {
252     terminate = true;
253     while (!done) {
254       synchronized (this) {
255         try {
256           wait(1);
257         } catch (Exception e) {
258           logger.log(Level.WARNING, "Exception terminating annealing.\n", e);
259         }
260       }
261     }
262   }
263 
264   private void begin() {
265     logger.info(String.format(" Initial temperature:    %8.3f (Kelvin)", schedule.getHighTemp()));
266     logger.info(String.format(" Final temperature:      %8.3f (Kelvin)", schedule.getLowTemp()));
267     logger.info(String.format(" Annealing steps:        %8d", schedule.getNumWindows()));
268     logger.info(String.format(" MD steps/temperature:   %8d", mdSteps));
269     logger.info(String.format(" MD time step:           %8.3f (fs)", timeStep));
270 
271     Thread annealingThread = new Thread(this);
272     annealingThread.start();
273     synchronized (this) {
274       try {
275         while (annealingThread.isAlive()) {
276           wait(100);
277         }
278       } catch (Exception e) {
279         String message = "Simulated annealing interrupted.";
280         logger.log(Level.WARNING, message, e);
281       }
282     }
283   }
284 
285   /** Represents non-composite AnnealingSchedules known (i.e. not FlatEndAnnealSchedule). */
286   public enum Schedules {
287     EXP(ExpAnnealSchedule::new, "EXP", "EXPONENTIAL"), LINEAR(LinearAnnealSchedule::new, "LINEAR");
288 
289     private final ScheduleConstructor sc;
290     private final Set<String> aliases;
291 
292     Schedules(ScheduleConstructor sc, String... names) {
293       this.sc = sc;
294       aliases = Set.of(names);
295     }
296 
297     /**
298      * Attempt to parse a String to a Schedules in a case-insensitive, alias-recognizing fashion.
299      *
300      * @param name Name of a schedule.
301      * @return A Schedules enum.
302      */
303     public static Schedules parse(String name) {
304       name = name.toUpperCase();
305       for (Schedules s : values()) {
306         if (s.aliases.contains(name)) {
307           return s;
308         }
309       }
310       return valueOf(name);
311     }
312 
313     /**
314      * Creates an AnnealingSchedule corresponding to this enum and provided values.
315      *
316      * @param nWindows Number of annealing windows.
317      * @param tLow Final temperature.
318      * @param tHigh Starting temperature.
319      * @return An AnnealingSchedule.
320      */
321     public AnnealingSchedule generate(int nWindows, double tLow, double tHigh) {
322       return sc.asConstruct(nWindows, tLow, tHigh);
323     }
324   }
325 
326   /** Functional interface corresponding to constructors of non-composite AnnealingSchedules. */
327   private interface ScheduleConstructor {
328 
329     AnnealingSchedule asConstruct(int nWindows, double tLow, double tHigh);
330   }
331 }