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.thermodynamics;
39  
40  import ffx.algorithms.AlgorithmListener;
41  import ffx.algorithms.cli.DynamicsOptions;
42  import ffx.algorithms.dynamics.MDVerbosity;
43  import ffx.algorithms.dynamics.MDWriteAction;
44  import ffx.algorithms.dynamics.MolecularDynamics;
45  import ffx.algorithms.dynamics.integrators.IntegratorEnum;
46  import ffx.algorithms.dynamics.thermostats.ThermostatEnum;
47  import ffx.algorithms.mc.BoltzmannMC;
48  import ffx.algorithms.mc.LambdaMove;
49  import ffx.algorithms.mc.MDMove;
50  import ffx.algorithms.thermodynamics.OrthogonalSpaceTempering.Histogram;
51  import ffx.numerics.Potential;
52  import ffx.potential.MolecularAssembly;
53  import ffx.potential.utils.EnergyException;
54  import org.apache.commons.configuration2.CompositeConfiguration;
55  
56  import java.io.File;
57  import java.util.EnumSet;
58  import java.util.logging.Level;
59  import java.util.logging.Logger;
60  
61  import static ffx.utilities.Constants.NS2SEC;
62  import static java.lang.String.format;
63  import static java.lang.System.arraycopy;
64  import static java.lang.System.nanoTime;
65  import static org.apache.commons.math3.util.FastMath.abs;
66  
67  /**
68   * Sample a thermodynamic path using the OST method, with the time-dependent bias built up using
69   * Metropolis Monte Carlo steps.
70   *
71   * <p>The algorithm generates coordinate (X) MC moves using molecular dynamics at a fixed lambda
72   * value (i.e. using OpenMM), followed by MC lambda moves.
73   *
74   * <p>1.) At a fixed Lambda, run a defined length MD trajectory to "move" coordinates and dU/dL on
75   * an approximate potential U* (i.e. no OST Bias).
76   *
77   * <p>2.) Accept / Reject the MD move with probability exp[-Beta(dU - dU*)] where dU is the change
78   * in AMOEBA + Bias energy and dU* is the change in AMOEBA + Kinetic energy from the MD.
79   *
80   * <p>3.) Randomly change the value of Lambda.
81   *
82   * <p>4.) Accept / Reject the Lambda move using the AMOEBA + OST Bias energy.
83   *
84   * <p>5.) Add to the time dependent 2D bias using the current values of Lambda and dU/dL.
85   *
86   * @author Michael J. Schnieders
87   * @author Hernan Beranbe
88   * @author Mallory R. Tollefson
89   * @author Jacob Litman
90   * @since 1.0
91   */
92  public class MonteCarloOST extends BoltzmannMC {
93  
94    /**
95     * Logger object to print out information for this class.
96     */
97    private static final Logger logger = Logger.getLogger(MonteCarloOST.class.getName());
98  
99    /**
100    * Controls the effect of verbose by logging at FINE vs. INFO.
101    */
102   private final Level mcLogLevel;
103   /**
104    * How verbose MD should be.
105    */
106   private final MDVerbosity mdVerbosity;
107 
108   /**
109    * The MD moves are only valid if energy is conserved. For this reason, energy drift is monitored.
110    */
111   private static final double ENERGY_CONSERVATION_TOLERANCE = 10.0;
112   /**
113    * Potential object used to retrieve the coordinates for the system.
114    */
115   private final Potential potential;
116   /**
117    * OST object used to retrieve OST energy throughout the simulation.
118    */
119   private final OrthogonalSpaceTempering orthogonalSpaceTempering;
120   /**
121    * MDMove object for completing MC-OST molecular dynamics moves.
122    */
123   private final MDMove mdMove;
124   /**
125    * Deposit a bias once every N MC cycles. Defaults to 1.
126    */
127   private final int biasDepositionFrequency;
128   /**
129    * Total number of steps to take for MC-OST sampling.
130    */
131   private long totalSteps;
132   /**
133    * Number of MD steps to take per MC-OST round.
134    */
135   private final long stepsPerMove;
136   /**
137    * Lambda move object for completing MC-OST lambda moves.
138    */
139   private final LambdaMove lambdaMove;
140   /**
141    * Double that keeps track of our lambda value.
142    */
143   private double lambda = 1.0;
144   /**
145    * Boolean that tells algorithm that we are in the equilibration phase of MC-OST.
146    */
147   private boolean equilibration = false;
148   /**
149    * True if MC-OST should handle writing out files.
150    */
151   private boolean automaticWriteouts = true;
152 
153   /**
154    * Constructor for MonteCarloOST.
155    *
156    * @param potentialEnergy          a {@link ffx.numerics.Potential} object.
157    * @param orthogonalSpaceTempering a {@link OrthogonalSpaceTempering} object.
158    * @param molecularAssembly        a {@link ffx.potential.MolecularAssembly} object.
159    * @param properties               a {@link org.apache.commons.configuration2.CompositeConfiguration}
160    *                                 object.
161    * @param listener                 a {@link ffx.algorithms.AlgorithmListener} object.
162    * @param dynamics                 CLI object containing key information.
163    * @param verbose                  Whether to be verbose.
164    * @param cycleLength              Length of an MC cycle in MD steps.
165    */
166   public MonteCarloOST(Potential potentialEnergy, OrthogonalSpaceTempering orthogonalSpaceTempering,
167                        MolecularAssembly molecularAssembly, CompositeConfiguration properties,
168                        AlgorithmListener listener, DynamicsOptions dynamics,
169                        boolean verbose, int cycleLength, File dynRestartFile) {
170     this.potential = potentialEnergy;
171     this.orthogonalSpaceTempering = orthogonalSpaceTempering;
172     mcLogLevel = verbose ? Level.INFO : Level.FINE;
173     mdVerbosity = verbose ? MDVerbosity.QUIET : MDVerbosity.SILENT;
174     stepsPerMove = cycleLength;
175     totalSteps = dynamics.getNumSteps();
176 
177     ThermostatEnum thermostat = dynamics.thermostat;
178     if (!thermostat.equals(ThermostatEnum.ADIABATIC)) {
179       logger.info(format(" MC-OST MD moves will use the Adiabatic thermostat (%s ignored).", thermostat));
180       dynamics.setThermostat(ThermostatEnum.ADIABATIC);
181     }
182 
183     IntegratorEnum integrator = dynamics.integrator;
184     if (!integrator.reversible || !integrator.deterministic) {
185       logger.info(format(" MC-OST MD moves require a reversible, deterministic integrator (Verlet replaced %s).", integrator));
186       dynamics.setIntegrator(IntegratorEnum.VERLET);
187     }
188 
189     // Create the MC MD and Lambda moves.
190     boolean useOST = properties.getBoolean("mdmove-full", false);
191     Potential mdPotential = useOST ? orthogonalSpaceTempering : potential;
192     mdMove = new MDMove(molecularAssembly, mdPotential, listener, dynamics, stepsPerMove, dynRestartFile);
193     if (properties.containsKey("randomseed")) {
194       int randomSeed = properties.getInt("randomseed", 0);
195       logger.info(format(" Setting random seed for lambdaMove to %d ", randomSeed));
196       lambdaMove = new LambdaMove(randomSeed, orthogonalSpaceTempering);
197       setRandomSeed(randomSeed);
198     } else {
199       lambdaMove = new LambdaMove(orthogonalSpaceTempering);
200     }
201 
202     // Configure discrete Lambda.
203     boolean discreteLambda = orthogonalSpaceTempering.getHistogram().hd.discreteLambda;
204     if (discreteLambda) {
205       lambdaMove.setContinuous(false);
206       // Set the move size to the Lambda bin width.
207       lambdaMove.setMoveSize(orthogonalSpaceTempering.getHistogram().hd.lambdaBinWidth);
208     }
209 
210     // The OST class will handle adding the time dependent bias.
211     orthogonalSpaceTempering.setPropagateLambda(false);
212     int frequency = properties.getInt("mc-ost-bias-frequency", 1);
213     if (frequency <= 1) {
214       biasDepositionFrequency = 1;
215     } else {
216       biasDepositionFrequency = frequency;
217       logger.info(format(" MC-OST will deposit a bias only once per %d MC cycles.", biasDepositionFrequency));
218     }
219   }
220 
221   /**
222    * Returns the current value of lambda
223    *
224    * @return lambda
225    */
226   public double getLambda() {
227     return lambda;
228   }
229 
230   /**
231    * Calls on the OST method set lambda to update lambda to the current value in this class
232    *
233    * @param lambda a double.
234    */
235   public void setLambda(double lambda) {
236     this.lambda = lambda;
237     orthogonalSpaceTempering.setLambda(lambda);
238   }
239 
240   public MolecularDynamics getMD() {
241     return mdMove.getMD();
242   }
243 
244   /**
245    * The goal is to sample lambda and coordinates (X) simultaneously to converge the ensemble average
246    * dU/dL for every state (lambda) along the thermodynamic path. Note that the order of 1 & 2 below
247    * can be swapped (i.e. run MD and then change lambda). Here the order is random for each trial.
248    * <p>
249    * 1.) Randomly change the value of Lambda.
250    * <p>
251    * 2.) At the proposed lambda, run a am MD trajectory to "move" coordinates and dU/dL.
252    * <p>
253    * 3.) Accept / Reject the Lambda + MD move using the total Hamiltonian (Kinetic energy + OST energy).
254    * <p>
255    * 4.) Add to the bias.
256    */
257   public void sampleOneStep() {
258     // Validate the starting value of lambda.
259     lambda = orthogonalSpaceTempering.getLambda();
260     lambda = lambdaMove.validateLambda(lambda);
261     orthogonalSpaceTempering.setLambda(lambda);
262 
263     int n = potential.getNumberOfVariables();
264     double[] gradient = new double[n];
265     double[] currentCoordinates = new double[n];
266     double[] proposedCoordinates = new double[n];
267     long numMoves = totalSteps / stepsPerMove;
268     int acceptMCOST = 0;
269 
270     // Initialize the current coordinates.
271     potential.getCoordinates(currentCoordinates);
272 
273     // The Histogram maintains the time-dependent bias.
274     Histogram histogram = orthogonalSpaceTempering.getHistogram();
275 
276     // Compute the Total OST Energy as the sum of the Force Field Energy and Bias Energy.
277     double currentOSTEnergy = orthogonalSpaceTempering.energyAndGradient(currentCoordinates, gradient);
278 
279     // Retrieve the computed dU/dL, Force Field Energy and Bias Energy.
280     double currentdUdL = orthogonalSpaceTempering.getForceFielddEdL();
281     double currentForceFieldEnergy = orthogonalSpaceTempering.getForceFieldEnergy();
282     double currentBiasEnergy = orthogonalSpaceTempering.getBiasEnergy();
283 
284     // Loop over the number of requested MC Moves.
285     for (int imove = 0; imove < numMoves; imove++) {
286       long totalMoveTime = -nanoTime();
287 
288       // Change lambda before or after the MD trajectory.
289       boolean lambdaBeforeMD = random.nextBoolean();
290 
291       if (logger.isLoggable(Level.FINE)) {
292         if (equilibration) {
293           logger.fine(format("\n Equilibration Round %d", imove + 1));
294         } else {
295           String moveType = lambdaBeforeMD ? "Single-step lambda plus MD move." : "Single-step MD plus lambda move.";
296           logger.fine(format("\n MC-OST Round %d: %s", imove + 1, moveType));
297         }
298         logger.fine(format(" Starting force field energy for move %16.8f", currentForceFieldEnergy));
299       }
300 
301       // Set the current and proposed lambda.
302       double currentLambda = orthogonalSpaceTempering.getLambda();
303       double proposedLambda;
304       if (equilibration) {
305         // The proposed lambda equals the current lambda because its frozen for equilibration.
306         proposedLambda = currentLambda;
307         singleStepMD();
308       } else {
309         if (lambdaBeforeMD) {
310           // Change lambda before MD.
311           proposedLambda = singleStepLambda();
312           singleStepMD();
313         } else {
314           // Change lambda after MD.
315           singleStepMD();
316           proposedLambda = singleStepLambda();
317         }
318       }
319 
320       // Get the starting and final kinetic energy for the MD move.
321       double currentKineticEnergy = mdMove.getInitialKinetic();
322       double proposedKineticEnergy = mdMove.getKineticEnergy();
323 
324       // Get the new coordinates.
325       potential.getCoordinates(proposedCoordinates);
326 
327       // Compute the Total OST energy and gradient as the sum of the force field energy and bias energy.
328       long proposedOSTEnergyTime = -nanoTime();
329       double proposedOSTEnergy;
330       try {
331         proposedOSTEnergy = orthogonalSpaceTempering.energyAndGradient(proposedCoordinates, gradient);
332       } catch (EnergyException e) {
333         mdMove.revertMove();
334         if (!equilibration) {
335           lambdaMove.revertMove();
336           lambda = currentLambda;
337         }
338         logger.log(Level.INFO, " Unstable MD Move skipped.");
339         continue;
340       }
341       proposedOSTEnergyTime += nanoTime();
342 
343       if (logger.isLoggable(Level.FINE)) {
344         logger.fine(format(" Time to complete MD OST energy method call %6.3f", proposedOSTEnergyTime * NS2SEC));
345       }
346 
347       // Retrieve the proposed dU/dL, force field energy and bias energy.
348       double proposeddUdL = orthogonalSpaceTempering.getForceFielddEdL();
349       double proposedForceFieldEnergy = orthogonalSpaceTempering.getForceFieldEnergy();
350       double proposedBiasEnergy = orthogonalSpaceTempering.getBiasEnergy();
351 
352       // The Metropolis criteria is based on the sum of the OST Energy and Kinetic Energy.
353       double currentTotalEnergy = currentOSTEnergy + currentKineticEnergy;
354       double proposedTotalEnergy = proposedOSTEnergy + proposedKineticEnergy;
355 
356       // Log energy differences for the proposed move.
357       if (logger.isLoggable(mcLogLevel)) {
358         logger.log(mcLogLevel, format("\n  %8s %12s %12s %12s %12s", "", "Kinetic", "Potential", "Bias", "Total"));
359         logger.log(mcLogLevel, format("  Current  %12.4f %12.4f %12.4f %12.4f", currentKineticEnergy,
360                 currentForceFieldEnergy, currentBiasEnergy, currentTotalEnergy));
361         logger.log(mcLogLevel, format("  Proposed %12.4f %12.4f %12.4f %12.4f", proposedKineticEnergy,
362                 proposedForceFieldEnergy, proposedBiasEnergy, proposedTotalEnergy));
363         logger.log(mcLogLevel, format("  Delta    %12.4f %12.4f %12.4f %12.4f",
364             proposedKineticEnergy - currentKineticEnergy,
365             proposedForceFieldEnergy - currentForceFieldEnergy, proposedBiasEnergy - currentBiasEnergy,
366             proposedTotalEnergy - currentTotalEnergy));
367       }
368 
369       // Monitor energy conservation.
370       double energyChange = mdMove.getEnergyChange();
371       if (abs(energyChange) > ENERGY_CONSERVATION_TOLERANCE) {
372         mdMove.revertMove();
373         if (!equilibration) {
374           lambdaMove.revertMove();
375           lambda = currentLambda;
376         }
377         logger.warning(" MC Move skipped due to lack of MD energy conservation.");
378         continue;
379       }
380 
381       // Initialize the result string to reflect rejection.
382       String result = "    ";
383       // Energy change.
384       double dE = proposedTotalEnergy - currentTotalEnergy;
385       if (equilibration) {
386         if (orthogonalSpaceTempering.insideHardWallConstraint(proposedLambda, proposeddUdL)
387             && evaluateMove(currentTotalEnergy, proposedTotalEnergy)) {
388           // Accept Equilibration MD move.
389           result = " -> ";
390           acceptMCOST++;
391           currentOSTEnergy = proposedOSTEnergy;
392           currentdUdL = proposeddUdL;
393           currentForceFieldEnergy = proposedForceFieldEnergy;
394           currentBiasEnergy = proposedBiasEnergy;
395           arraycopy(proposedCoordinates, 0, currentCoordinates, 0, n);
396         } else {
397           mdMove.revertMove();
398         }
399       } else {
400         if (orthogonalSpaceTempering.insideHardWallConstraint(proposedLambda, proposeddUdL)
401             && evaluateMove(currentTotalEnergy, proposedTotalEnergy)) {
402           // Accept MD = Lambda move.
403           result = " -> ";
404           acceptMCOST++;
405           lambda = proposedLambda;
406           currentdUdL = proposeddUdL;
407           currentForceFieldEnergy = proposedForceFieldEnergy;
408           arraycopy(proposedCoordinates, 0, currentCoordinates, 0, n);
409         } else {
410           lambdaMove.revertMove();
411           lambda = currentLambda;
412           mdMove.revertMove();
413         }
414         if (imove % biasDepositionFrequency == 0) {
415           histogram.addBias(currentdUdL);
416           if (logger.isLoggable(Level.FINE)) {
417             logger.fine(format(" Added Bias at move %d: [ L=%5.3f, FL=%9.3f] ", imove + 1, lambda, currentdUdL));
418           }
419         } else {
420           logger.info(format(" Cycle %d: skipping bias deposition.", imove + 1));
421         }
422 
423         // Compute the updated OST bias.
424         currentBiasEnergy = histogram.computeBiasEnergy(lambda, currentdUdL);
425 
426         // Update the current OST Energy to be the sum of the current force field energy and updated OST Bias.
427         currentOSTEnergy = currentForceFieldEnergy + currentBiasEnergy;
428 
429         boolean snapShot = lambda >= orthogonalSpaceTempering.getLambdaWriteOut();
430         long mdMoveNum = (imove + 1) * stepsPerMove;
431         orthogonalSpaceTempering.getHistogram().ld.stepsTaken += stepsPerMove;
432         EnumSet<MDWriteAction> written = mdMove.writeFilesForStep(mdMoveNum, snapShot, true);
433         if (written.contains(MDWriteAction.RESTART)) {
434           orthogonalSpaceTempering.writeAdditionalRestartInfo(false);
435         }
436       }
437 
438       // Log the move.
439       totalMoveTime += nanoTime();
440       double percent = (acceptMCOST * 100.0) / (imove + 1);
441       logger.info(format(" %4d [ L=%5.3f, dU/dL=%8.3f]%s[ L=%5.3f, dU/dL=%8.3f] ΔE=%12.4f (%5.1f%%) in %6.3f sec.",
442           imove + 1, currentLambda, currentdUdL, result,
443           proposedLambda, proposeddUdL, dE, percent, totalMoveTime * NS2SEC));
444     }
445   }
446 
447   /**
448    * The goal is to sample lambda and coordinates (X) separately to converge the ensemble average
449    * dU/dL for every state (lambda) along the thermodynamic path.
450    * <p>
451    * 1.) At a fixed lambda, run a defined length MD trajectory to "move" coordinates and dU/dL.
452    * <p>
453    * 2.) Accept / Reject the MD move using the total Hamiltonian (Kinetic energy + OST energy).
454    * <p>
455    * 3.) Randomly change the value of Lambda.
456    * <p>
457    * 4.) Accept / Reject the Lambda move using the OST energy.
458    * <p>
459    * 5.) Add a hill to the histogram.
460    */
461   public void sampleTwoStep() {
462     // Validate the starting value of lambda.
463     lambda = orthogonalSpaceTempering.getLambda();
464     lambda = lambdaMove.validateLambda(lambda);
465     orthogonalSpaceTempering.setLambda(lambda);
466 
467     int n = potential.getNumberOfVariables();
468     double[] gradient = new double[n];
469     double[] currentCoordinates = new double[n];
470     double[] proposedCoordinates = new double[n];
471     long numMoves = totalSteps / stepsPerMove;
472     int acceptLambda = 0;
473     int acceptMD = 0;
474 
475     // Initialize the current coordinates.
476     potential.getCoordinates(currentCoordinates);
477 
478     // Update time dependent bias.
479     Histogram histogram = orthogonalSpaceTempering.getHistogram();
480 
481     // Compute the current OST potential energy.
482     double currentOSTEnergy = orthogonalSpaceTempering.energyAndGradient(currentCoordinates, gradient);
483 
484     // Collect the current dU/dL, Force Field Energy and Bias Energy.
485     double currentdUdL = orthogonalSpaceTempering.getForceFielddEdL();
486     double currentForceFieldEnergy = orthogonalSpaceTempering.getForceFieldEnergy();
487     double currentBiasEnergy = orthogonalSpaceTempering.getBiasEnergy();
488 
489     // Initialize MC move instances.
490     for (int imove = 0; imove < numMoves; imove++) {
491       long totalMoveTime = -nanoTime();
492       long mdMoveAndEvalTime = -nanoTime();
493 
494       if (logger.isLoggable(Level.FINE)) {
495         if (equilibration) {
496           logger.fine(format("\n MD Equilibration Round %d", imove + 1));
497         } else {
498           logger.fine(format("\n MC Orthogonal Space Sampling Round %d: Independent Steps", imove + 1));
499         }
500       }
501 
502       // Run MD in an approximate potential U* (U star) that does not include the OST bias.
503       long mdMoveTime = -nanoTime();
504       mdMove.move(mdVerbosity);
505       mdMoveTime += nanoTime();
506       logger.log(mcLogLevel, format("  Total time for MD move: %6.3f", mdMoveTime * NS2SEC));
507 
508       // Get the starting and final kinetic energy for the MD move.
509       double currentKineticEnergy = mdMove.getInitialKinetic();
510       double proposedKineticEnergy = mdMove.getKineticEnergy();
511 
512       // Get the new coordinates.
513       potential.getCoordinates(proposedCoordinates);
514 
515       // Compute the Total OST Energy as the sum of the Force Field Energy and Bias Energy.
516       long proposedOSTEnergyTime = -nanoTime();
517 
518       double proposedOSTEnergy;
519       try {
520         proposedOSTEnergy = orthogonalSpaceTempering.energyAndGradient(proposedCoordinates, gradient);
521       } catch (EnergyException e) {
522         mdMove.revertMove();
523         logger.log(Level.INFO, " Unstable MD Move skipped.");
524         continue;
525       }
526       proposedOSTEnergyTime += nanoTime();
527 
528       logger.fine(format("  Time to complete MD OST energy method call %6.3f", proposedOSTEnergyTime * NS2SEC));
529 
530       // Retrieve the proposed dU/dL, Force Field Energy and Bias Energy.
531       double proposeddUdL = orthogonalSpaceTempering.getForceFielddEdL();
532       double proposedForceFieldEnergy = orthogonalSpaceTempering.getForceFieldEnergy();
533       double proposedBiasEnergy = orthogonalSpaceTempering.getBiasEnergy();
534 
535       // The Metropolis criteria is based on the sum of the OST Energy and Kinetic Energy.
536       double currentTotalEnergy = currentOSTEnergy + currentKineticEnergy;
537       double proposedTotalEnergy = proposedOSTEnergy + proposedKineticEnergy;
538 
539       if (logger.isLoggable(mcLogLevel)) {
540         logger.log(mcLogLevel, format("\n  %8s %12s %12s %12s %12s", "", "Kinetic", "Potential", "Bias", "Total"));
541         logger.log(mcLogLevel, format("  Current  %12.4f %12.4f %12.4f %12.4f", currentKineticEnergy,
542             currentForceFieldEnergy, currentBiasEnergy, currentTotalEnergy));
543         logger.log(mcLogLevel, format("  Proposed %12.4f %12.4f %12.4f %12.4f", proposedKineticEnergy,
544             proposedForceFieldEnergy, proposedBiasEnergy, proposedTotalEnergy));
545         logger.log(mcLogLevel, format("  Delta    %12.4f %12.4f %12.4f %12.4f",
546             proposedKineticEnergy - currentKineticEnergy,
547             proposedForceFieldEnergy - currentForceFieldEnergy, proposedBiasEnergy - currentBiasEnergy,
548             proposedTotalEnergy - currentTotalEnergy));
549       }
550 
551       double energyChange = mdMove.getEnergyChange();
552       if (abs(energyChange) > ENERGY_CONSERVATION_TOLERANCE) {
553         mdMove.revertMove();
554         logger.warning(" MC Move skipped due to lack of MD energy conservation");
555         continue;
556       }
557 
558       String result = "    ";
559       double dE = proposedTotalEnergy - currentTotalEnergy;
560       if (orthogonalSpaceTempering.insideHardWallConstraint(orthogonalSpaceTempering.getLambda(),
561           proposeddUdL) && evaluateMove(currentTotalEnergy, proposedTotalEnergy)) {
562         // Accept MD move.
563         acceptMD++;
564         result = " -> ";
565         currentOSTEnergy = proposedOSTEnergy;
566         currentdUdL = proposeddUdL;
567         currentForceFieldEnergy = proposedForceFieldEnergy;
568         currentBiasEnergy = proposedBiasEnergy;
569         arraycopy(proposedCoordinates, 0, currentCoordinates, 0, n);
570       } else {
571         mdMove.revertMove();
572       }
573 
574       // Log the move.
575       mdMoveAndEvalTime += nanoTime();
576       double percent = (acceptMD * 100.0) / (imove + 1);
577       logger.info(format(" %4d MD [ L=%5.3f, dU/dL=%8.3f]%s[ L=%5.3f, dU/dL=%8.3f] ΔE=%12.4f (%5.1f%%) in %6.3f sec.", imove + 1,
578           lambda, currentdUdL, result, lambda, proposeddUdL, dE, percent, mdMoveAndEvalTime * NS2SEC));
579 
580       // During equilibration, do not change Lambda or contribute to the OST bias.
581       if (!equilibration) {
582         // Update Lambda.
583         long lambdaMoveTime = -nanoTime();
584         double currentLambda = orthogonalSpaceTempering.getLambda();
585         lambdaMove.move();
586         double proposedLambda = orthogonalSpaceTempering.getLambda();
587 
588         // Compute the Total OST Energy as the sum of the Force Field Energy and Bias Energy.
589         long proposedOSTEnergyTime2 = -nanoTime();
590         proposedOSTEnergy = orthogonalSpaceTempering.energyAndGradient(currentCoordinates, gradient);
591         proposedOSTEnergyTime2 += nanoTime();
592 
593         // Retrieve the proposed dU/dL, Force Field Energy and Bias Energy.
594         proposedForceFieldEnergy = orthogonalSpaceTempering.getForceFieldEnergy();
595         proposeddUdL = orthogonalSpaceTempering.getForceFielddEdL();
596 
597         if (logger.isLoggable(mcLogLevel)) {
598           logger.log(mcLogLevel, format("\n  Time to complete Lambda OST energy method call %6.3f ", proposedOSTEnergyTime2 * NS2SEC));
599           logger.log(mcLogLevel, format("  Current  OST     %12.3f at L=%5.3f.", currentOSTEnergy, currentLambda));
600           logger.log(mcLogLevel, format("  Proposed OST     %12.3f at L=%5.3f.", proposedOSTEnergy, proposedLambda));
601           logger.log(mcLogLevel, format("  MC Energy change: %12.3f (kcal/mol).", proposedOSTEnergy - currentOSTEnergy));
602         }
603 
604         dE = proposedOSTEnergy - currentOSTEnergy;
605         if (orthogonalSpaceTempering.insideHardWallConstraint(proposedLambda, proposeddUdL)
606             && evaluateMove(currentOSTEnergy, proposedOSTEnergy)) {
607           acceptLambda++;
608           result = " -> ";
609           currentForceFieldEnergy = proposedForceFieldEnergy;
610           currentdUdL = proposeddUdL;
611           lambda = proposedLambda;
612         } else {
613           result = "    ";
614           lambdaMove.revertMove();
615           lambda = currentLambda;
616         }
617 
618         lambdaMoveTime += nanoTime();
619         percent = (acceptLambda * 100.0) / (imove + 1);
620         logger.info(format("      L  [ L=%5.3f, dU/dL=%8.3f]%s[ L=%5.3f, dU/dL=%8.3f] ΔE=%12.4f (%5.1f%%) in %6.3f sec.",
621             currentLambda, currentdUdL, result, proposedLambda, proposeddUdL, dE, percent, lambdaMoveTime * NS2SEC));
622 
623         if (imove % biasDepositionFrequency == 0) {
624           histogram.addBias(currentdUdL);
625           logger.fine(format(" Added Bias at move %d: [ L=%5.3f, FL=%9.3f] ", imove + 1, lambda, currentdUdL));
626         } else {
627           logger.info(format(" Cycle %d: skipping bias deposition.", imove + 1));
628         }
629 
630         // Compute the updated OST bias.
631         currentBiasEnergy = histogram.computeBiasEnergy(lambda, currentdUdL);
632 
633         // Update the current OST Energy to be the sum of the current Force Field Energy and updated
634         // OST Bias.
635         currentOSTEnergy = currentForceFieldEnergy + currentBiasEnergy;
636 
637         if (automaticWriteouts) {
638           long mdMoveNum = (imove + 1) * stepsPerMove;
639           boolean trySnapshot = lambda >= orthogonalSpaceTempering.getLambdaWriteOut();
640           EnumSet<MDWriteAction> written = mdMove.writeFilesForStep(mdMoveNum, trySnapshot, true);
641           if (written.contains(MDWriteAction.RESTART)) {
642             orthogonalSpaceTempering.writeAdditionalRestartInfo(false);
643           }
644         }
645       }
646 
647       if (logger.isLoggable(Level.FINE)) {
648         totalMoveTime += nanoTime();
649         logger.fine(format(" Round complete in %6.3f sec.", totalMoveTime * NS2SEC));
650       }
651     }
652   }
653 
654   public void setAutomaticWriteouts(boolean autoWrite) {
655     automaticWriteouts = autoWrite;
656   }
657 
658   /**
659    * Sets the value of the boolean equilibration variables to true or false to either allow an
660    * equilibration step or skip it.
661    *
662    * @param equilibration a boolean.
663    */
664   public void setEquilibration(boolean equilibration) {
665     this.equilibration = equilibration;
666   }
667 
668   /**
669    * Calls on LambdaMove class method setLambdaStdDev to update the lambda standard deviation to the
670    * current value in this class
671    *
672    * @param stdDev a double.
673    */
674   public void setLambdaStdDev(double stdDev) {
675     if (!lambdaMove.isContinuous()) {
676       if (stdDev != orthogonalSpaceTempering.getHistogram().hd.lambdaBinWidth) {
677         logger.info(
678             format(" Requested Lambda step size change %6.4f is not equal to OST bin width %6.4f.",
679                 stdDev, orthogonalSpaceTempering.getHistogram().hd.lambdaBinWidth));
680         return;
681       }
682     }
683     lambdaMove.setMoveSize(stdDev);
684   }
685 
686   /**
687    * Sets the next number of steps MC-OST should run.
688    *
689    * @param totalSteps The length of the next MC-OST run.
690    */
691   public void setTotalSteps(long totalSteps) {
692     this.totalSteps = totalSteps;
693   }
694 
695   /**
696    * Propose a lambda move.
697    *
698    * @return The proposed lambda.
699    */
700   private double singleStepLambda() {
701     lambdaMove.move();
702     double proposedLambda = orthogonalSpaceTempering.getLambda();
703     logger.log(mcLogLevel, format(" Proposed lambda: %5.3f.", proposedLambda));
704     return proposedLambda;
705   }
706 
707   /**
708    * Run MD in an approximate potential U* (U star) that does not include the OST bias.
709    */
710   private void singleStepMD() {
711     long mdMoveTime = -nanoTime();
712     mdMove.move(mdVerbosity);
713     mdMoveTime += nanoTime();
714     logger.log(mcLogLevel, format(" Total time for MD move: %6.3f", mdMoveTime * NS2SEC));
715   }
716 
717   /**
718    * {@inheritDoc}
719    */
720   @Override
721   public void revertStep() {
722     throw new UnsupportedOperationException("Not supported yet.");
723   }
724 
725   /**
726    * {@inheritDoc}
727    */
728   @Override
729   protected double currentEnergy() {
730     throw new UnsupportedOperationException("Not supported yet.");
731   }
732 
733   /**
734    * {@inheritDoc}
735    */
736   @Override
737   protected void storeState() {
738     throw new UnsupportedOperationException("Not supported yet.");
739   }
740 }