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 edu.rit.mp.DoubleBuf;
41  import edu.rit.pj.Comm;
42  import ffx.algorithms.AlgorithmListener;
43  import ffx.algorithms.cli.DynamicsOptions;
44  import ffx.algorithms.cli.LambdaParticleOptions;
45  import ffx.algorithms.dynamics.Barostat;
46  import ffx.algorithms.dynamics.integrators.Stochastic;
47  import ffx.algorithms.optimize.Minimize;
48  import ffx.crystal.Crystal;
49  import ffx.crystal.CrystalPotential;
50  import ffx.numerics.Potential;
51  import ffx.numerics.integrate.DataSet;
52  import ffx.numerics.integrate.DoublesDataSet;
53  import ffx.numerics.integrate.Integrate1DNumeric;
54  import ffx.numerics.integrate.Integrate1DNumeric.IntegrationType;
55  import ffx.potential.MolecularAssembly;
56  import ffx.potential.SystemState;
57  import ffx.potential.Utilities;
58  import ffx.potential.bonded.LambdaInterface;
59  import ffx.potential.parsers.PDBFilter;
60  import ffx.potential.parsers.SystemFilter;
61  import ffx.potential.parsers.XYZFilter;
62  import org.apache.commons.configuration2.CompositeConfiguration;
63  import org.apache.commons.io.FilenameUtils;
64  
65  import javax.annotation.Nullable;
66  import java.io.File;
67  import java.io.PrintWriter;
68  import java.util.ArrayList;
69  import java.util.Arrays;
70  import java.util.List;
71  import java.util.Optional;
72  import java.util.logging.Level;
73  import java.util.logging.Logger;
74  
75  import static ffx.numerics.integrate.Integrate1DNumeric.IntegrationType.SIMPSONS;
76  import static ffx.utilities.Constants.R;
77  import static java.lang.String.format;
78  import static java.lang.System.arraycopy;
79  import static java.util.Arrays.fill;
80  import static org.apache.commons.math3.util.FastMath.PI;
81  import static org.apache.commons.math3.util.FastMath.abs;
82  import static org.apache.commons.math3.util.FastMath.asin;
83  import static org.apache.commons.math3.util.FastMath.exp;
84  import static org.apache.commons.math3.util.FastMath.floor;
85  import static org.apache.commons.math3.util.FastMath.sin;
86  import static org.apache.commons.math3.util.FastMath.sqrt;
87  
88  /**
89   * An implementation of the Orthogonal Space Tempering algorithm.
90   *
91   * <p>This only partially implements the LambdaInterface, since it does not return 2nd lambda
92   * derivatives. The 2nd derivatives of the bias require 3rd derivatives of the underlying Hamiltonian
93   * (not these derivatives are not needed for OST MD).
94   *
95   * @author Michael J. Schnieders, James Dama, Wei Yang and Pengyu Ren
96   * @since 1.0
97   */
98  public class OrthogonalSpaceTempering implements CrystalPotential, LambdaInterface {
99  
100   private static final Logger logger = Logger.getLogger(OrthogonalSpaceTempering.class.getName());
101   /**
102    * The potential energy of the system.
103    */
104   protected final CrystalPotential potential;
105   /**
106    * Reference to the Barostat in use; if present this must be turned off during optimization.
107    */
108   protected final Barostat barostat;
109   /**
110    * The AlgorithmListener is called each time a count is added.
111    */
112   protected final AlgorithmListener algorithmListener;
113   /**
114    * Print detailed energy information.
115    */
116   protected final boolean print = false;
117   /**
118    * Number of variables.
119    */
120   protected final int nVariables;
121   /**
122    * A potential energy that implements the LambdaInterface.
123    */
124   private final LambdaInterface lambdaInterface;
125   /**
126    * List of additional Histograms this OST can switch to.
127    */
128   private final List<Histogram> allHistograms = new ArrayList<>();
129   /**
130    * Parameters to control saving local optimizations.
131    */
132   private final OptimizationParameters optimizationParameters;
133   /**
134    * Properties.
135    */
136   private final CompositeConfiguration properties;
137   /**
138    * The MolecularAssembly being simulated.
139    */
140   protected MolecularAssembly molecularAssembly;
141   /**
142    * Are FAST varying energy terms being computed, SLOW varying energy terms, or BOTH. OST is not
143    * active when only FAST varying energy terms are being propagated.
144    */
145   protected Potential.STATE state = Potential.STATE.BOTH;
146   /**
147    * Force Field Potential Energy (i.e. with no bias terms added).
148    */
149   protected double forceFieldEnergy;
150   /**
151    * Contains counts for the OST bias.
152    */
153   private Histogram histogram;
154   /**
155    * Index of the current Histogram.
156    */
157   private int histogramIndex;
158   /**
159    * Flag to indicate that the Lambda particle should be propagated.
160    */
161   private boolean propagateLambda = true;
162   /**
163    * Mixed second partial derivative with respect to coordinates and lambda.
164    */
165   private final double[] dUdXdL;
166 
167   /**
168    * Gradient array needed when the OST Energy method is called.
169    */
170   private final double[] tempGradient;
171 
172   /**
173    * Partial derivative of the force field energy with respect to lambda.
174    */
175   private double dForceFieldEnergydL;
176   /**
177    * Magnitude of the 2D orthogonal space bias G(L,dE/dL).
178    */
179   private double gLdEdL = 0.0;
180   /**
181    * OST Bias energy.
182    */
183   private double biasEnergy;
184   /**
185    * Total system energy.
186    */
187   private double totalEnergy;
188   /**
189    * Total partial derivative of the potential (U) being sampled with respect to lambda.
190    */
191   private double dUdLambda;
192   /**
193    * Second partial derivative of the potential being sampled with respect to lambda.
194    */
195   private double d2UdL2;
196   /**
197    * If true, values of (lambda, dU/dL) that have not been observed are rejected.
198    */
199   private boolean hardWallConstraint = false;
200 
201   private final DynamicsOptions dynamicsOptions;
202 
203   private final LambdaParticleOptions lambdaParticleOptions;
204 
205   /**
206    * OST Constructor.
207    *
208    * @param lambdaInterface       defines Lambda and dU/dL.
209    * @param potential             defines the Potential energy.
210    * @param histogramData         contains histogram restart data.
211    * @param lambdaData            contains lambda restart data.
212    * @param properties            defines System properties.
213    * @param dynamicsOptions       defines molecular dynamics parameters.
214    * @param lambdaParticleOptions defines lambda particle parameters.
215    * @param algorithmListener     the AlgorithmListener to be notified of progress.
216    */
217   public OrthogonalSpaceTempering(LambdaInterface lambdaInterface, CrystalPotential potential,
218                                   HistogramData histogramData, LambdaData lambdaData, CompositeConfiguration properties,
219                                   DynamicsOptions dynamicsOptions, LambdaParticleOptions lambdaParticleOptions,
220                                   AlgorithmListener algorithmListener) {
221     this.lambdaInterface = lambdaInterface;
222     this.potential = potential;
223     this.properties = properties;
224     this.dynamicsOptions = dynamicsOptions;
225     this.lambdaParticleOptions = lambdaParticleOptions;
226     this.algorithmListener = algorithmListener;
227     nVariables = potential.getNumberOfVariables();
228 
229     if (potential instanceof Barostat) {
230       barostat = (Barostat) potential;
231     } else {
232       barostat = null;
233     }
234 
235     dUdXdL = new double[nVariables];
236     tempGradient = new double[nVariables];
237 
238     // Init the Histogram.
239     histogram = new Histogram(properties, histogramData, lambdaData);
240     histogramIndex = 0;
241     allHistograms.add(histogram);
242 
243     // Configure optimization parameters.
244     optimizationParameters = new OptimizationParameters(properties);
245   }
246 
247   /**
248    * Add an alternate Histogram this OST can use.
249    *
250    * @param histogramData Settings to use for the new Histogram.
251    */
252   public void addHistogram(HistogramData histogramData, LambdaData lambdaData) {
253     Histogram newHisto = new Histogram(properties, histogramData, lambdaData);
254     histogramData.asynchronous = this.histogram.hd.asynchronous;
255     allHistograms.add(newHisto);
256   }
257 
258   /**
259    * {@inheritDoc}
260    */
261   @Override
262   public boolean dEdLZeroAtEnds() {
263     return false;
264   }
265 
266   /**
267    * {@inheritDoc}
268    */
269   @Override
270   public boolean destroy() {
271     // Shut down the CountReceiveThread.
272     histogram.destroy();
273     return potential.destroy();
274   }
275 
276   /**
277    * Compute the force field + bias energy.
278    */
279   public double energy(double[] x) {
280 
281     // OST is propagated with the slowly varying terms.
282     if (state == Potential.STATE.FAST) {
283       forceFieldEnergy = potential.energy(x);
284       return forceFieldEnergy;
285     }
286 
287     // Stochastic dynamics pre-force propagation.
288     if (propagateLambda) {
289       histogram.stochasticPreForce();
290     }
291 
292     // We have to compute the energy and gradient, since we need dU/dL to be computed.
293     fill(tempGradient, 0.0);
294     forceFieldEnergy = potential.energyAndGradient(x, tempGradient);
295 
296     dForceFieldEnergydL = lambdaInterface.getdEdL();
297     d2UdL2 = lambdaInterface.getd2EdL2();
298     int lambdaBin = histogram.indexForLambda();
299     dUdLambda = dForceFieldEnergydL;
300 
301     gLdEdL = 0.0;
302     double bias1D;
303     // Update the free energy difference.
304     histogram.updateFreeEnergyDifference(false, false);
305     if (histogram.hd.metaDynamics) {
306       bias1D = histogram.energyAndGradientMeta(true);
307     } else {
308       // Calculate recursion kernel G(L, F_L) and its derivatives with respect to L and F_L.
309       if (histogram.hd.biasMag > 0.0) {
310         double[] chainRule = new double[2];
311         gLdEdL = histogram.energyAndGradient2D(dUdLambda, chainRule);
312         double dGdLambda = chainRule[0];
313         double dGdFLambda = chainRule[1];
314         dUdLambda += dGdLambda + dGdFLambda * d2UdL2;
315       }
316 
317       // Compute the energy and gradient for the recursion worker at F(L) using interpolation.
318       bias1D = histogram.energyAndGradient1D(true);
319     }
320 
321     // The total bias energy is the sum of the 1D and 2D terms.
322     biasEnergy = bias1D + gLdEdL;
323 
324     if (print) {
325       logger.info(format(" Bias Energy        %16.8f", biasEnergy));
326       logger.info(format(" %s %16.8f  (Kcal/mole)", "OST Potential    ", forceFieldEnergy + biasEnergy));
327     }
328 
329     if (propagateLambda) {
330       histogram.stochasticPostForce();
331 
332       histogram.ld.stepsTaken++;
333       long energyCount = histogram.ld.stepsTaken;
334 
335       // Log the current Lambda state.
336       int printFrequency = dynamicsOptions.getReportFrequency(100);
337       if (energyCount % printFrequency == 0) {
338         double dBdL = dUdLambda - dForceFieldEnergydL;
339         double lambda = histogram.ld.lambda;
340         int lambdaBins = histogram.hd.getLambdaBins();
341         if (lambdaBins < 1000) {
342           logger.info(format(" L=%6.4f (%3d) F_LU=%10.4f F_LB=%10.4f F_L=%10.4f V_L=%10.4f", lambda,
343               lambdaBin, dForceFieldEnergydL, dBdL, dUdLambda, histogram.ld.thetaVelocity));
344         } else {
345           logger.info(format(" L=%6.4f (%4d) F_LU=%10.4f F_LB=%10.4f F_L=%10.4f V_L=%10.4f", lambda,
346               lambdaBin, dForceFieldEnergydL, dBdL, dUdLambda, histogram.ld.thetaVelocity));
347         }
348       }
349 
350       // Metadynamics grid counts (every 'countInterval' steps).
351       if (energyCount % histogram.hd.countInterval == 0) {
352         histogram.addBias(dForceFieldEnergydL);
353 
354         // Locally optimize the current state.
355         if (optimizationParameters.doOptimization) {
356           optimizationParameters.optimize(forceFieldEnergy, x, tempGradient);
357         }
358 
359         if (algorithmListener != null) {
360           algorithmListener.algorithmUpdate(molecularAssembly);
361         }
362       }
363     }
364 
365     totalEnergy = forceFieldEnergy + biasEnergy;
366 
367     return totalEnergy;
368   }
369 
370   /**
371    * {@inheritDoc}
372    */
373   @Override
374   public double energyAndGradient(double[] x, double[] gradient) {
375 
376     // Stochastic dynamics pre-force propagation.
377     if (state != STATE.FAST && propagateLambda) {
378       histogram.stochasticPreForce();
379     }
380 
381     // Compute the force field energy and gradient.
382     forceFieldEnergy = potential.energyAndGradient(x, gradient);
383 
384     // OST is propagated with the slowly varying terms.
385     if (state == STATE.FAST) {
386       return forceFieldEnergy;
387     }
388 
389     // dU/dL is the partial derivative of the force field energy with respect to lambda.
390     dForceFieldEnergydL = lambdaInterface.getdEdL();
391     dUdLambda = dForceFieldEnergydL;
392     d2UdL2 = lambdaInterface.getd2EdL2();
393 
394     gLdEdL = 0.0;
395     double bias1D;
396     // Update the free energy difference.
397     histogram.updateFreeEnergyDifference(false, false);
398     if (histogram.hd.metaDynamics) {
399       bias1D = histogram.energyAndGradientMeta(true);
400     } else {
401       if (histogram.hd.biasMag > 0) {
402         double[] chainRule = new double[2];
403         gLdEdL = histogram.energyAndGradient2D(dUdLambda, chainRule);
404         double dGdLambda = chainRule[0];
405         double dGdFLambda = chainRule[1];
406 
407         // Lambda gradient due to recursion kernel G(L, F_L).
408         dUdLambda += dGdLambda + dGdFLambda * d2UdL2;
409 
410         // Cartesian coordinate gradient due to recursion kernel G(L, F_L).
411         fill(dUdXdL, 0.0);
412         lambdaInterface.getdEdXdL(dUdXdL);
413         for (int i = 0; i < nVariables; i++) {
414           gradient[i] += dGdFLambda * dUdXdL[i];
415         }
416       }
417 
418       // Compute the energy and gradient for the recursion worker at F(L) using interpolation.
419       bias1D = histogram.energyAndGradient1D(true);
420     }
421 
422     // The total bias is the sum of 1D and 2D terms.
423     biasEnergy = bias1D + gLdEdL;
424 
425     if (print) {
426       logger.info(format(" %s %16.8f", "Bias Energy       ", biasEnergy));
427       logger.info(format(" %s %16.8f  %s", "OST Potential    ", forceFieldEnergy + biasEnergy, "(Kcal/mole)"));
428     }
429 
430     if (propagateLambda) {
431       histogram.stochasticPostForce();
432 
433       histogram.ld.stepsTaken++;
434       long energyCount = histogram.ld.stepsTaken;
435 
436       // Log the current Lambda state.
437       int printFrequency = dynamicsOptions.getReportFrequency(100);
438       if (energyCount % printFrequency == 0) {
439         double dBdL = dUdLambda - dForceFieldEnergydL;
440         int lambdaBin = histogram.indexForLambda();
441         double lambda = histogram.ld.lambda;
442         int lambdaBins = histogram.hd.getLambdaBins();
443         if (lambdaBins < 1000) {
444           logger.info(format(" L=%6.4f (%3d) F_LU=%10.4f F_LB=%10.4f F_L=%10.4f V_L=%10.4f", lambda,
445               lambdaBin, dForceFieldEnergydL, dBdL, dUdLambda, histogram.ld.thetaVelocity));
446         } else {
447           logger.info(format(" L=%6.4f (%4d) F_LU=%10.4f F_LB=%10.4f F_L=%10.4f V_L=%10.4f", lambda,
448               lambdaBin, dForceFieldEnergydL, dBdL, dUdLambda, histogram.ld.thetaVelocity));
449         }
450       }
451 
452       // Metadynamics grid counts (every 'countInterval' steps).
453       if (energyCount % histogram.hd.countInterval == 0) {
454         histogram.addBias(dForceFieldEnergydL);
455 
456         // Locally optimize the current state.
457         if (optimizationParameters.doOptimization) {
458           optimizationParameters.optimize(forceFieldEnergy, x, gradient);
459         }
460 
461         if (algorithmListener != null) {
462           algorithmListener.algorithmUpdate(molecularAssembly);
463         }
464       }
465     }
466 
467     totalEnergy = forceFieldEnergy + biasEnergy;
468 
469     return totalEnergy;
470   }
471 
472   /**
473    * {@inheritDoc}
474    */
475   @Override
476   public double[] getAcceleration(double[] acceleration) {
477     return potential.getAcceleration(acceleration);
478   }
479 
480   public Histogram[] getAllHistograms() {
481     int nHisto = allHistograms.size();
482     Histogram[] ret = new Histogram[nHisto];
483     ret = allHistograms.toArray(ret);
484     return ret;
485   }
486 
487   /**
488    * {@inheritDoc}
489    */
490   @Override
491   public double[] getCoordinates(double[] doubles) {
492     return potential.getCoordinates(doubles);
493   }
494 
495   /**
496    * {@inheritDoc}
497    */
498   @Override
499   public Crystal getCrystal() {
500     return potential.getCrystal();
501   }
502 
503   /**
504    * {@inheritDoc}
505    */
506   @Override
507   public void setCrystal(Crystal crystal) {
508     potential.setCrystal(crystal);
509   }
510 
511   /**
512    * Returns the number of energy evaluations performed by this OST, including those picked up in the
513    * lambda file.
514    *
515    * @return Number of energy steps taken by this walker.
516    */
517   public long getEnergyCount() {
518     return histogram.ld.stepsTaken;
519   }
520 
521   /**
522    * {@inheritDoc}
523    */
524   @Override
525   public Potential.STATE getEnergyTermState() {
526     return state;
527   }
528 
529   /**
530    * {@inheritDoc}
531    */
532   @Override
533   public void setEnergyTermState(Potential.STATE state) {
534     this.state = state;
535     potential.setEnergyTermState(state);
536   }
537 
538   /**
539    * Getter for the field <code>forceFieldEnergy</code>.
540    *
541    * @return a double.
542    */
543   public double getForceFieldEnergy() {
544     return forceFieldEnergy;
545   }
546 
547   /**
548    * Return the current 2D Histogram of counts.
549    *
550    * @return the Histogram.
551    */
552   public Histogram getHistogram() {
553     return histogram;
554   }
555 
556   /**
557    * Getter for the field <code>lambda</code>.
558    *
559    * @return a double.
560    */
561   public double getLambda() {
562     return histogram.ld.lambda;
563   }
564 
565   /**
566    * Setter for the field <code>lambda</code>.
567    *
568    * @param lambda a double.
569    */
570   public void setLambda(double lambda) {
571     if (histogram != null) {
572       lambda = histogram.mapLambda(lambda);
573     } else {
574       logger.warning(" OrthogonalSpaceTempering.setLambda was called before histogram constructed!");
575       logger.info(Utilities.stackTraceToString(new RuntimeException()));
576     }
577     lambdaInterface.setLambda(lambda);
578     histogram.ld.lambda = lambda;
579     histogram.ld.theta = asin(sqrt(lambda));
580   }
581 
582   /**
583    * {@inheritDoc}
584    */
585   @Override
586   public double[] getMass() {
587     return potential.getMass();
588   }
589 
590   /**
591    * {@inheritDoc}
592    */
593   @Override
594   public int getNumberOfVariables() {
595     return potential.getNumberOfVariables();
596   }
597 
598   /**
599    * Return the OST optimization information.
600    *
601    * @return The OST optimization parameters.
602    */
603   public OptimizationParameters getOptimizationParameters() {
604     return optimizationParameters;
605   }
606 
607   /**
608    * getPotentialEnergy.
609    *
610    * @return a {@link ffx.numerics.Potential} object.
611    */
612   public Potential getPotentialEnergy() {
613     return potential;
614   }
615 
616   /**
617    * {@inheritDoc}
618    */
619   @Override
620   public double[] getPreviousAcceleration(double[] previousAcceleration) {
621     return potential.getPreviousAcceleration(previousAcceleration);
622   }
623 
624   /**
625    * {@inheritDoc}
626    */
627   @Override
628   public double[] getScaling() {
629     return potential.getScaling();
630   }
631 
632   /**
633    * {@inheritDoc}
634    */
635   @Override
636   public void setScaling(double[] scaling) {
637     potential.setScaling(scaling);
638   }
639 
640   /**
641    * {@inheritDoc}
642    */
643   @Override
644   public double getTotalEnergy() {
645     return totalEnergy;
646   }
647 
648   /**
649    * getTotaldEdLambda.
650    *
651    * @return a double.
652    */
653   public double getTotaldEdLambda() {
654     return dUdLambda;
655   }
656 
657   /**
658    * {@inheritDoc}
659    *
660    * <p>Return a reference to each variables type.
661    */
662   @Override
663   public Potential.VARIABLE_TYPE[] getVariableTypes() {
664     return potential.getVariableTypes();
665   }
666 
667   /**
668    * {@inheritDoc}
669    */
670   @Override
671   public double[] getVelocity(double[] velocity) {
672     return potential.getVelocity(velocity);
673   }
674 
675   /**
676    * {@inheritDoc}
677    */
678   @Override
679   public double getd2EdL2() {
680     throw new UnsupportedOperationException(
681         " Second derivatives of the bias are not implemented, as they require third derivatives of the potential.");
682   }
683 
684   /**
685    * {@inheritDoc}
686    */
687   @Override
688   public double getdEdL() {
689     return getTotaldEdLambda();
690   }
691 
692   /**
693    * {@inheritDoc}
694    */
695   @Override
696   public void getdEdXdL(double[] gradient) {
697     throw new UnsupportedOperationException(
698         " Second derivatives of the bias are not implemented, as they require third derivatives of the potential.");
699   }
700 
701   // TODO: Delete method when debugging of RepexOST is done.
702   public void logOutputFiles(int index) {
703     logger.info(format(" OST: Lambda file %s, histogram %s", histogram.ld.getLambdaFile(),
704         allHistograms.get(index).hd.getHistogramFile()));
705   }
706 
707   /**
708    * {@inheritDoc}
709    */
710   @Override
711   public void setAcceleration(double[] acceleration) {
712     potential.setAcceleration(acceleration);
713   }
714 
715   /**
716    * If this flag is true, (lambda, dU/dL) Monte Carlo samples that have no weight in the Histogram
717    * are rejected.
718    *
719    * @param hardWallConstraint If true, MC samples outside the current range are rejected.
720    */
721   public void setHardWallConstraint(boolean hardWallConstraint) {
722     this.hardWallConstraint = hardWallConstraint;
723   }
724 
725   public void setMolecularAssembly(MolecularAssembly molecularAssembly) {
726     this.molecularAssembly = molecularAssembly;
727   }
728 
729   /**
730    * {@inheritDoc}
731    */
732   @Override
733   public void setPreviousAcceleration(double[] previousAcceleration) {
734     potential.setPreviousAcceleration(previousAcceleration);
735   }
736 
737   /**
738    * Indicate if the Lambda extended system particle should be propagated using Langevin dynamics.
739    *
740    * @param propagateLambda If true, Lambda will be propagated using Langevin dynamics.
741    */
742   public void setPropagateLambda(boolean propagateLambda) {
743     this.propagateLambda = propagateLambda;
744   }
745 
746   /**
747    * If true, the Lambda extended system particle is propagated using Langevin dynamics.
748    */
749   public boolean getPropagateLambda() {
750     return propagateLambda;
751   }
752 
753   /**
754    * {@inheritDoc}
755    */
756   @Override
757   public void setVelocity(double[] velocity) {
758     potential.setVelocity(velocity);
759   }
760 
761   /**
762    * Switch to an alternate Histogram.
763    *
764    * @param index Index of the Histogram to use.
765    */
766   public void switchHistogram(int index) {
767     histogramIndex = index;
768     histogram = allHistograms.get(histogramIndex);
769     logger.info(" OST switching to histogram " + histogramIndex);
770   }
771 
772   @Override
773   public void writeAdditionalRestartInfo(boolean recursive) {
774     histogram.writeRestart();
775     if (recursive) {
776       potential.writeAdditionalRestartInfo(recursive);
777     }
778   }
779 
780   double getLambdaWriteOut() {
781     return histogram.hd.resetHistogramAtLambda;
782   }
783 
784   /**
785    * getForceFielddEdL.
786    *
787    * @return a double.
788    */
789   double getForceFielddEdL() {
790     return dForceFieldEnergydL;
791   }
792 
793   /**
794    * Getter for the field <code>biasEnergy</code>.
795    *
796    * @return a double.
797    */
798   double getBiasEnergy() {
799     return biasEnergy;
800   }
801 
802   /**
803    * If the dUdLHardWall flag is set to true, this method will return false if the (lambda, dU/dL)
804    * sample is has not been seen.
805    *
806    * @param lambda The proposed lambda value.
807    * @param dUdL   The proposed dU/dL value.
808    * @return Returns false only if the dUdLHardWall flag is true, and the (lambda, dU/dL) sample has
809    * not been seen.
810    */
811   boolean insideHardWallConstraint(double lambda, double dUdL) {
812     if (hardWallConstraint) {
813       double weight = histogram.getRecursionKernelValue(lambda, dUdL);
814       return weight > 0.0;
815     }
816     return true;
817   }
818 
819   /**
820    * Parameters for running local optimizations during OST sampling.
821    */
822   public class OptimizationParameters {
823 
824     /**
825      * Flag to turn on OST optimization.
826      *
827      * <p>The default doOptimization = false.
828      */
829     private boolean doOptimization = false;
830     /**
831      * Reset unit cell parameters, molecular orientation and translation.
832      */
833     private final boolean doUnitCellReset;
834     /**
835      * OST optimization only runs if Lambda is greater than the lambdaCutoff.
836      *
837      * <p>The default lambdaCutoff = 0.8.
838      */
839     private final double lambdaCutoff;
840     /**
841      * The lowest energy found via optimizations.
842      *
843      * <p>The optimumEnergy is initially set to Double.MAX_VALUE.
844      */
845     private double optimumEnergy = Double.MAX_VALUE;
846     /**
847      * The OST optimization frequency
848      *
849      * <p>The default is once every 10,000 steps.
850      */
851     private final int frequency;
852     /**
853      * The OST optimization convergence criteria.
854      *
855      * <p>The default eps = 0.1.
856      */
857     private final double eps;
858     /**
859      * The OST tolerance when checking for equal energy after coordinate reversion.
860      *
861      * <p>The default is 1.0e-8 kcal/mol.
862      */
863     private final double tolerance;
864     /**
865      * The OST optimization energy window.
866      *
867      * <p>The default is 4.0 kcal/mol, which is convenient for small organic crystals.
868      */
869     private final double energyWindow;
870     /**
871      * Holds the lowest potential energy coordinates.
872      */
873     private double[] optimumCoords;
874     /**
875      * File instance used for saving optimized structures.
876      */
877     private File optimizationFile;
878     /**
879      * SystemFilter used to save optimized structures.
880      */
881     private SystemFilter optimizationFilter;
882 
883     /**
884      * Empty constructor.
885      */
886     OptimizationParameters(CompositeConfiguration properties) {
887       energyWindow = properties.getDouble("ost-opt-energy-window", 10.0);
888       eps = properties.getDouble("ost-opt-eps", 0.1);
889       tolerance = properties.getDouble("ost-opt-tolerance", 1.0e-8);
890       frequency = properties.getInt("ost-opt-frequency", 10000);
891       lambdaCutoff = properties.getDouble("ost-opt-lambda-cutoff", 0.8);
892       doUnitCellReset = properties.getBoolean("ost-opt-unitcell-reset", false);
893     }
894 
895     private void log() {
896       logger.info("\n Optimization Parameters");
897       logger.info(format("  Energy Window:                  %6.3f (kcal/mol)", energyWindow));
898       logger.info(format("  EPS:                            %6.4f RMS (kcal/mol/Ã…)", eps));
899       logger.info(format("  Tolerance:                      %6.4f (kcal/mol)", tolerance));
900       logger.info(format("  Frequency:                      %6d (steps)", frequency));
901       logger.info(format("  Lambda Cutoff:                  %6.4f", lambdaCutoff));
902       logger.info(format("  Unit Cell Reset:                %6B", doUnitCellReset));
903       logger.info(format("  File:                           %s", optimizationFile.getName()));
904     }
905 
906     /**
907      * getOptimumCoordinates.
908      *
909      * @return an array of {@link double} objects.
910      */
911     public double[] getOptimumCoordinates() {
912       if (optimumEnergy < Double.MAX_VALUE) {
913         return optimumCoords;
914       } else {
915         logger.info(
916             "Lambda optimization cutoff was not reached. Try increasing the number of timesteps.");
917         return null;
918       }
919     }
920 
921     /**
922      * getOptimumEnergy.
923      *
924      * @return a double.
925      */
926     public double getOptimumEnergy() {
927       if (optimumEnergy == Double.MAX_VALUE) {
928         logger.info(
929             "Lambda optimization cutoff was not reached. Try increasing the number of timesteps.");
930       }
931       return optimumEnergy;
932     }
933 
934     /**
935      * Run a local optimization.
936      *
937      * @param e        Current energy.
938      * @param x        Current atomic coordinates.
939      * @param gradient Work array for collecting the gradient.
940      */
941     public void optimize(double e, double[] x, @Nullable double[] gradient) {
942 
943       // Return if the optimization flag is not set, or if lambda is not beyond the cutoff.
944       double lambda = histogram.ld.lambda;
945       long stepsTaken = histogram.ld.stepsTaken;
946       if (doOptimization && lambda > lambdaCutoff && stepsTaken % frequency == 0) {
947         if (gradient == null) {
948           gradient = new double[x.length];
949         }
950       } else {
951         return;
952       }
953 
954       logger.info(format("\n OST Minimization (Step %d)", stepsTaken));
955 
956       // Set the underlying Potential's Lambda value to 1.0.
957       lambdaInterface.setLambda(1.0);
958 
959       // Use all energy terms.
960       potential.setEnergyTermState(Potential.STATE.BOTH);
961 
962       // Turn off the Barostat.
963       boolean origBaroActive = true;
964       if (barostat != null) {
965         origBaroActive = barostat.isActive();
966         barostat.setActive(false);
967       }
968 
969       // Optimize the system.
970       try {
971         double startingEnergy = potential.energy(x);
972         Minimize minimize = new Minimize(molecularAssembly, potential, algorithmListener);
973         minimize.minimize(eps);
974         // Collect the minimum energy.
975         double minEnergy = potential.getTotalEnergy();
976         // Check for a new minimum within an energy window of the lowest energy structure found.
977         if (minEnergy < optimumEnergy + energyWindow) {
978           if (minEnergy < optimumEnergy) {
979             optimumEnergy = minEnergy;
980           }
981           int n = potential.getNumberOfVariables();
982           optimumCoords = new double[n];
983           optimumCoords = potential.getCoordinates(optimumCoords);
984           double mass = molecularAssembly.getMass();
985           Crystal crystal = molecularAssembly.getCrystal();
986           double density = crystal.getDensity(mass);
987           optimizationFilter.writeFile(optimizationFile, true);
988           Crystal uc = crystal.getUnitCell();
989           logger.info(format(" Minimum: %12.6f %s (%12.6f g/cc) optimized from %12.6f at step %d.",
990               minEnergy, uc.toShortString(), density, startingEnergy, stepsTaken));
991         }
992       } catch (Exception ex) {
993         String message = ex.getMessage();
994         logger.info(
995             format(" Exception minimizing coordinates at lambda=%8.6f\n %s.", lambda, message));
996         logger.info(" Sampling will continue.");
997       }
998 
999       // Set the underlying Potential's Lambda value back to current lambda value.
1000       lambdaInterface.setLambda(lambda);
1001 
1002       // Reset the Potential State
1003       potential.setEnergyTermState(state);
1004 
1005       // Reset the Barostat
1006       if (barostat != null) {
1007         barostat.setActive(origBaroActive);
1008       }
1009 
1010       if (doUnitCellReset) {
1011         logger.info("\n Resetting Unit Cell");
1012         double mass = molecularAssembly.getMass();
1013         double density = molecularAssembly.getCrystal().getDensity(mass);
1014         molecularAssembly.applyRandomDensity(density);
1015         molecularAssembly.applyRandomSymOp(0.0);
1016         lambda = 0.0;
1017         lambdaInterface.setLambda(lambda);
1018       } else {
1019         // Revert to the coordinates and gradient prior to optimization.
1020         double eCheck = potential.energyAndGradient(x, gradient);
1021 
1022         if (abs(eCheck - e) > tolerance) {
1023           logger.warning(
1024               format(" Optimization could not revert coordinates %16.8f vs. %16.8f.", e, eCheck));
1025         }
1026       }
1027     }
1028 
1029     /**
1030      * setOptimization.
1031      *
1032      * @param doOptimization    a boolean.
1033      * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
1034      */
1035     public void setOptimization(boolean doOptimization, MolecularAssembly molecularAssembly) {
1036       this.doOptimization = doOptimization;
1037       OrthogonalSpaceTempering.this.molecularAssembly = molecularAssembly;
1038       File file = molecularAssembly.getFile();
1039       String fileName = FilenameUtils.removeExtension(file.getAbsolutePath());
1040       String ext = FilenameUtils.getExtension(file.getAbsolutePath());
1041       if (optimizationFilter == null) {
1042         if (ext.toUpperCase().contains("XYZ")) {
1043           optimizationFile = new File(fileName + "_opt.arc");
1044           optimizationFilter = new XYZFilter(optimizationFile, molecularAssembly,
1045               molecularAssembly.getForceField(), molecularAssembly.getProperties());
1046         } else {
1047           optimizationFile = new File(fileName + "_opt.pdb");
1048           PDBFilter pdbFilter = new PDBFilter(optimizationFile, molecularAssembly,
1049               molecularAssembly.getForceField(), molecularAssembly.getProperties());
1050           int models = pdbFilter.countNumModels();
1051           pdbFilter.setModelNumbering(models);
1052           optimizationFilter = pdbFilter;
1053         }
1054       }
1055 
1056       if (this.doOptimization) {
1057         log();
1058       }
1059     }
1060   }
1061 
1062   /**
1063    * Store and operate on a 2D Histogram of (Lambda, dU/dL) observations to produce an OST bias.
1064    *
1065    * @author Michael J. Schnieders
1066    * @since 1.0
1067    */
1068   public class Histogram {
1069 
1070     /**
1071      * If a 2D bias is in use (i.e. lambda-bias-cutoff > 0), do not normalize bias height.
1072      */
1073     private static final double TWO_D_NORMALIZATION = 1.0;
1074     /**
1075      * If a 1D bias is in use (i.e. lambda-bias-cutoff == 0), normalize bias height to deposit the same
1076      * volume of bias.
1077      */
1078     private static final double ONE_D_NORMALIZATION = Math.sqrt(2.0 * Math.PI);
1079     /**
1080      * If the real bias magnitude is 0 (for no 2D bias), temporarily set biasMag to this value to calculate the ensemble average dU/dL.
1081      *
1082      * <p>Any value that does not overflow/underflow double precision summations of the count matrix
1083      * will give identical results. For example, values of 1.0e-20, 1.0 and 1.0e20 were tested and
1084      * found to give identical results.
1085      *
1086      * <p>Thus, a value of 1.0 is good choice.
1087      */
1088     private static final double PSEUDO_BIAS_MAGNITUDE = 1.0;
1089     /**
1090      * Parallel Java world communicator.
1091      */
1092     protected final Comm world;
1093     /**
1094      * Rank of this process.
1095      */
1096     protected final int rank;
1097     /**
1098      * 1D PMF with respect to lambda F(L).
1099      */
1100     final double[] ensembleAveragedUdL;
1101     /**
1102      * This deltaT is used to determine the tempering weight as described below for the
1103      * temperingWeight variable.
1104      *
1105      * <p>deltaT = temperingFactor * kB * T.
1106      */
1107     private final double deltaT;
1108     /**
1109      * The integration algorithm used for thermodynamic integration.
1110      */
1111     private final IntegrationType integrationType;
1112     /**
1113      * Send OST counts asynchronously.
1114      */
1115     private final SendAsynchronous sendAsynchronous;
1116     /**
1117      * Send OST counts synchronously.
1118      */
1119     private final SendSynchronous sendSynchronous;
1120     /**
1121      * The Dama et al. transition-tempering weight. The initial temperingWeight = 1.0,
1122      * and more generally temperingWeight = exp(-biasHeight/deltaT)
1123      */
1124     private double temperingWeight = 1.0;
1125     /**
1126      * If the recursion kernel becomes too large or too small for some combinations of (Lambda,
1127      * dU/dL), then its statistical weight = exp(kernel * beta) cannot be represented by a double
1128      * value.
1129      */
1130     private double[] kernelValues;
1131     /**
1132      * Most recent lambda values for each Walker.
1133      */
1134     private double lastReceivedLambda;
1135     /**
1136      * Most recent dU/dL value for each walker.
1137      */
1138     private double lastReceiveddUdL;
1139     private final boolean spreadBias;
1140     /**
1141      * The HistogramData object contains the parameters for the Histogram that are saved to disk.
1142      */
1143     final HistogramData hd;
1144     /**
1145      * The LambdaData object contains the parameters for the Lambda particle that are saved to disk.
1146      */
1147     final LambdaData ld;
1148     /**
1149      * The Stochastic integrator used to propagate the lambda particle.
1150      */
1151     private final Stochastic stochastic;
1152     /**
1153      * The state of the lambda particle.
1154      */
1155     private final SystemState lambdaState;
1156     /**
1157      * Number of hills when the last free energy estimate was made.
1158      */
1159     private int currentNumberOfHills = 0;
1160     /**
1161      * The current free energy difference.
1162      */
1163     private double currentFreeEnergyDifference = 0.0;
1164 
1165     /**
1166      * Histogram constructor.
1167      *
1168      * @param properties    a CompositeConfiguration used to configure the Histogram.
1169      * @param histogramData An object containing the values this Histogram will be set to.
1170      */
1171     Histogram(CompositeConfiguration properties, HistogramData histogramData, LambdaData lambdaData) {
1172       hd = histogramData;
1173       ld = lambdaData;
1174 
1175       /*
1176       double gaussNormalization;
1177       if (hd.lambdaBiasCutoff == 0 && !hd.wasHistogramRead()) {
1178         gaussNormalization = ONE_D_NORMALIZATION;
1179         logger.info(format(" Bias magnitude multiplied by a factor of %.4f "
1180             + "sqrt(2*pi) to match 1D Gaussian volume to 2D Gaussian volume.", gaussNormalization));
1181       } else {
1182         gaussNormalization = TWO_D_NORMALIZATION;
1183       }
1184       biasMag = settings.getBiasMag() * gaussNormalization;
1185        */
1186 
1187       deltaT = hd.temperingFactor * R * dynamicsOptions.getTemperature();
1188 
1189       // Allocate space to compute the <dU/dL>
1190       ensembleAveragedUdL = new double[hd.getLambdaBins()];
1191 
1192       // Allocate space to regularize kernel values.
1193       kernelValues = new double[hd.getDUDLBins()];
1194 
1195       String propString = properties.getString("ost-integrationType", "SIMPSONS");
1196       IntegrationType testType;
1197       try {
1198         testType = IntegrationType.valueOf(propString.toUpperCase());
1199       } catch (Exception ex) {
1200         logger.warning(format(" Invalid argument %s to ost-integrationType; resetting to SIMPSONS", propString));
1201         testType = SIMPSONS;
1202       }
1203       integrationType = testType;
1204 
1205       spreadBias = properties.getBoolean("ost-spread-bias", false);
1206 
1207       /*
1208        Set up the multi-walker communication variables for Parallel Java
1209        communication between nodes.
1210       */
1211       world = Comm.world();
1212       int numProc = world.size();
1213       rank = world.rank();
1214       if (hd.asynchronous) {
1215         // Use asynchronous communication.
1216         sendAsynchronous = new SendAsynchronous(this);
1217         sendAsynchronous.start();
1218         sendSynchronous = null;
1219       } else {
1220         Histogram[] histograms = new Histogram[numProc];
1221         int[] rankToHistogramMap = new int[numProc];
1222         for (int i = 0; i < numProc; i++) {
1223           histograms[i] = this;
1224           rankToHistogramMap[i] = 0;
1225         }
1226         sendSynchronous = new SendSynchronous(histograms, rankToHistogramMap);
1227         sendAsynchronous = null;
1228       }
1229 
1230       lastReceivedLambda = ld.lambda;
1231       if (hd.discreteLambda) {
1232         lastReceivedLambda = mapLambda(lastReceivedLambda);
1233         logger.info(format(" Discrete lambda: initializing lambda to nearest bin %.5f", lastReceivedLambda));
1234         ld.lambda = lastReceivedLambda;
1235         ld.theta = asin(sqrt(lastReceivedLambda));
1236         lambdaInterface.setLambda(lastReceivedLambda);
1237         lambdaState = null;
1238         stochastic = null;
1239       } else {
1240         // Configure the Stochastic integrator.
1241         lambdaState = new SystemState(1);
1242         stochastic = new Stochastic(lambdaParticleOptions.getLambdaFriction(), lambdaState);
1243         stochastic.setTemperature(dynamicsOptions.getTemperature());
1244         stochastic.setTimeStep(dynamicsOptions.getDtPsec());
1245         double[] mass = lambdaState.getMass();
1246         double[] thetaPosition = lambdaState.x();
1247         double[] thetaVelocity = lambdaState.v();
1248         double[] thetaAccel = lambdaState.a();
1249         mass[0] = lambdaParticleOptions.getLambdaMass();
1250         thetaPosition[0] = ld.theta;
1251         thetaVelocity[0] = ld.thetaVelocity;
1252         thetaAccel[0] = ld.thetaAcceleration;
1253       }
1254 
1255       lastReceiveddUdL = getdEdL();
1256 
1257       if (hd.wasHistogramRead()) {
1258         updateFreeEnergyDifference(true, false);
1259       }
1260     }
1261 
1262     public void disableResetStatistics() {
1263       hd.resetHistogram = false;
1264     }
1265 
1266     /**
1267      * evaluateTotalBias.
1268      *
1269      * @param bias If false, return the negative of the Total OST bias.
1270      * @return A StringBuffer The total OST Bias.
1271      */
1272     public StringBuffer evaluateTotalOSTBias(boolean bias) {
1273       StringBuffer sb = new StringBuffer();
1274       double[] chainRule = new double[2];
1275       for (int dUdLBin = 0; dUdLBin < hd.getDUDLBins(); dUdLBin++) {
1276         double currentdUdL = dUdLforBin(dUdLBin);
1277         sb.append(format(" %16.8f", currentdUdL));
1278         for (int lambdaBin = 0; lambdaBin < hd.getLambdaBins(); lambdaBin++) {
1279           double currentLambda = lambdaForIndex(lambdaBin);
1280           double bias1D = energyAndGradient1D(currentLambda, false);
1281           double bias2D = energyAndGradient2D(currentLambda, currentdUdL, chainRule, hd.biasMag);
1282           double totalBias = bias1D + bias2D;
1283           // If the bias flag is false, turn the total bias into the PMF.
1284           if (!bias) {
1285             totalBias = -totalBias;
1286           }
1287           sb.append(format(" %16.8f", totalBias));
1288         }
1289         sb.append("\n");
1290       }
1291       return sb;
1292     }
1293 
1294     /**
1295      * evaluate2DOSTBias.
1296      *
1297      * @param bias If false, return the negative of the 2D OST bias.
1298      * @return A StringBuffer with the 2D OST bias.
1299      */
1300     public StringBuffer evaluate2DOSTBias(boolean bias) {
1301       StringBuffer sb = new StringBuffer();
1302       double[] chainRule = new double[2];
1303       for (int dUdLBin = 0; dUdLBin < hd.getDUDLBins(); dUdLBin++) {
1304         double currentdUdL = dUdLforBin(dUdLBin);
1305         sb.append(format(" %16.8f", currentdUdL));
1306         for (int lambdaBin = 0; lambdaBin < hd.getLambdaBins(); lambdaBin++) {
1307           double currentLambda = lambdaForIndex(lambdaBin);
1308           double bias2D = energyAndGradient2D(currentLambda, currentdUdL, chainRule, hd.biasMag);
1309           if (!bias) {
1310             bias2D = -bias2D;
1311           }
1312           sb.append(format(" %16.8f", bias2D));
1313         }
1314         sb.append("\n");
1315       }
1316       return sb;
1317     }
1318 
1319     /**
1320      * For MPI parallel jobs, returns true if the walkers are independent (i.e. contribute to only
1321      * their own histogram).
1322      *
1323      * @return True if the walkers are independent.
1324      */
1325     public boolean getIndependentWalkers() {
1326       return hd.independentWalkers;
1327     }
1328 
1329     public double getLambdaResetValue() {
1330       return hd.resetHistogramAtLambda;
1331     }
1332 
1333     /**
1334      * For MPI parallel jobs, return the rank of this process.
1335      *
1336      * @return The rank of this process.
1337      */
1338     public int getRank() {
1339       return rank;
1340     }
1341 
1342     public boolean getResetStatistics() {
1343       return hd.resetHistogram;
1344     }
1345 
1346     /**
1347      * Return the SynchronousSend associated with this Histogram, if any.
1348      *
1349      * @return The SynchronousSend, if any.
1350      */
1351     public Optional<SendSynchronous> getSynchronousSend() {
1352       return Optional.ofNullable(sendSynchronous);
1353     }
1354 
1355     public void setLastReceiveddUdL(double lastReceiveddUdL) {
1356       this.lastReceiveddUdL = lastReceiveddUdL;
1357     }
1358 
1359     public double updateFreeEnergyDifference(boolean print, boolean save) {
1360       if (hd.metaDynamics) {
1361         return updateMetaDynamicsFreeEnergyDifference(print, save);
1362       } else {
1363         return updateOSTFreeEnergyDifference(print, save);
1364       }
1365     }
1366 
1367     /**
1368      * Update the free energy estimate for Meta Dynamics.
1369      *
1370      * @param print Whether to write the histogram to screen.
1371      * @param save  Whether to write the histogram to disk.
1372      * @return Free energy (via integration of ensemble-average dU/dL)
1373      */
1374     public double updateMetaDynamicsFreeEnergyDifference(boolean print, boolean save) {
1375       synchronized (this) {
1376         if (!print && currentNumberOfHills == hd.counts) {
1377           return currentFreeEnergyDifference;
1378         }
1379         double freeEnergyDifferenceMeta = 0.0;
1380         double freeEnergyDifferenceTI = 0.0;
1381 
1382         double minFL = Double.MAX_VALUE;
1383 
1384         int lambdaBins = hd.getLambdaBins();
1385         double[] metaFreeEnergy = new double[lambdaBins];
1386 
1387         // Total histogram weight.
1388         double totalWeight = 0;
1389         StringBuilder stringBuilder = new StringBuilder();
1390 
1391         // Loop over lambda bins, computing <dU/dL> for each bin.
1392         for (int lambdaBin = 0; lambdaBin < lambdaBins; lambdaBin++) {
1393           int firstdUdLBin = firstdUdLBin(lambdaBin);
1394           int lastdUdLBin = lastdUdLBin(lambdaBin);
1395           double lambdaCount = 0;
1396           // The dUdL range sampled for lambda.
1397           double mindUdLForLambda = 0.0;
1398           double maxdUdLforLambda = 0.0;
1399           double maxBias = 0;
1400           if (firstdUdLBin == -1 || lastdUdLBin == -1) {
1401             ensembleAveragedUdL[lambdaBin] = 0.0;
1402             minFL = 0.0;
1403           } else {
1404             double ensembleAverageFLambda = 0.0;
1405             double partitionFunction = 0.0;
1406 
1407             // Evaluate and regularize all kernel values for this value of lambda.
1408             double offset = evaluateKernelForLambda(lambdaBin, firstdUdLBin, lastdUdLBin);
1409 
1410             for (int dUdLBin = firstdUdLBin; dUdLBin <= lastdUdLBin; dUdLBin++) {
1411               double kernel = kernelValues[dUdLBin];
1412 
1413               if (kernel - offset > maxBias) {
1414                 maxBias = kernel - offset;
1415               }
1416 
1417               // The weight is just the kernel value for no 2D bias.
1418               partitionFunction += kernel;
1419 
1420               double currentdUdL = dUdLforBin(dUdLBin);
1421               ensembleAverageFLambda += currentdUdL * kernel;
1422               lambdaCount += getRecursionKernelValue(lambdaBin, dUdLBin);
1423             }
1424             if (minFL > maxBias) {
1425               minFL = maxBias;
1426             }
1427             ensembleAveragedUdL[lambdaBin] = (partitionFunction == 0) ? 0 : ensembleAverageFLambda / partitionFunction;
1428             mindUdLForLambda = hd.dUdLMinimum + firstdUdLBin * hd.dUdLBinWidth;
1429             maxdUdLforLambda = hd.dUdLMinimum + (lastdUdLBin + 1) * hd.dUdLBinWidth;
1430           }
1431 
1432           double deltaFreeEnergy = ensembleAveragedUdL[lambdaBin] * deltaForLambdaBin(lambdaBin);
1433           freeEnergyDifferenceTI += deltaFreeEnergy;
1434           totalWeight += lambdaCount;
1435 
1436 
1437           double lambdaBinWidth = hd.lambdaBinWidth;
1438           double llL = lambdaBin * lambdaBinWidth - hd.lambdaBinWidth_2;
1439           double ulL = llL + lambdaBinWidth;
1440           if (llL < 0.0) {
1441             llL = 0.0;
1442           }
1443           if (ulL > 1.0) {
1444             ulL = 1.0;
1445           }
1446 
1447           double midLambda = llL;
1448           if (!hd.discreteLambda) {
1449             midLambda = (llL + ulL) / 2.0;
1450           }
1451           double bias1D = energyAndGradient1D(midLambda, false);
1452           metaFreeEnergy[lambdaBin] = energyAndGradientMeta(midLambda, false);
1453           freeEnergyDifferenceMeta = -(metaFreeEnergy[lambdaBin] - metaFreeEnergy[0]);
1454 
1455           if (print || save) {
1456             stringBuilder.append(format(" %6.2e %7.5f %7.1f %7.1f %9.2f %9.2f %9.2f %9.2f %9.2f\n", lambdaCount,
1457                 midLambda, mindUdLForLambda, maxdUdLforLambda, ensembleAveragedUdL[lambdaBin],
1458                 bias1D, freeEnergyDifferenceTI, metaFreeEnergy[lambdaBin], freeEnergyDifferenceMeta));
1459           }
1460         }
1461 
1462         double temperingOffset = hd.getTemperingOffset();
1463         double temperEnergy = (minFL > temperingOffset) ? temperingOffset - minFL : 0;
1464         temperingWeight = exp(temperEnergy / deltaT);
1465 
1466         if (print) {
1467           logger.info("  Weight   Lambda      dU/dL Bins   <dU/dL>      g(L) dG_OST(L)  Meta(L) dG_Meta(L)");
1468           logger.info(stringBuilder.toString());
1469           logger.info(" Histogram Evaluation");
1470           logger.info(format("  Free Energy Difference: %12.4f kcal/mol", freeEnergyDifferenceMeta));
1471           logger.info(format("  Number of Hills:        %12d", hd.counts));
1472           logger.info(format("  Total Bias Added:       %12.4f kcal/mol", totalWeight * hd.biasMag));
1473           logger.info(format("  Minimum Bias:           %12.4f kcal/mol", minFL));
1474           logger.info(format("  Tempering Percentage:   %12.4f %%\n", temperingWeight * 100));
1475         }
1476 
1477         if (save) {
1478           String modelFilename = molecularAssembly.getFile().getAbsolutePath();
1479           File saveDir = new File(FilenameUtils.getFullPath(modelFilename));
1480           String dirName = saveDir + File.separator;
1481           String fileName = dirName + "histogram.txt";
1482           try {
1483             logger.info(" Writing " + fileName);
1484             PrintWriter printWriter = new PrintWriter(fileName);
1485             printWriter.write(stringBuilder.toString());
1486             printWriter.close();
1487           } catch (Exception e) {
1488             logger.info(format(" Failed to write %s.", fileName));
1489           }
1490         }
1491 
1492         currentNumberOfHills = hd.counts;
1493         currentFreeEnergyDifference = freeEnergyDifferenceMeta;
1494         return currentFreeEnergyDifference;
1495       }
1496     }
1497 
1498     /**
1499      * Eqs. 7 and 8 from the 2012 Crystal Thermodynamics paper.
1500      *
1501      * @param print Whether to write the histogram to screen.
1502      * @param save  Whether to write the histogram to disk.
1503      * @return Free energy (via integration of ensemble-average dU/dL)
1504      */
1505     public double updateOSTFreeEnergyDifference(boolean print, boolean save) {
1506       synchronized (this) {
1507         if (!print && currentNumberOfHills == hd.counts) {
1508           return currentFreeEnergyDifference;
1509         }
1510 
1511         double freeEnergyDifferenceOST = 0.0;
1512         double minFL = Double.MAX_VALUE;
1513 
1514         // If the bias magnitude is zero, computing <dU/dL> from
1515         // counts will not be correct. Assign a temporary non-zero bias magnitude.
1516         boolean biasMagZero = hd.biasMag <= 0.0;
1517 
1518         // Total histogram weight.
1519         double totalWeight = 0;
1520         double beta = 1.0 / (R * dynamicsOptions.getTemperature());
1521         StringBuilder stringBuilder = new StringBuilder();
1522 
1523         // Loop over lambda bins, computing <dU/dL> for each bin.
1524         for (int lambdaBin = 0; lambdaBin < hd.lambdaBins; lambdaBin++) {
1525           int firstdUdLBin = firstdUdLBin(lambdaBin);
1526           int lastdUdLBin = lastdUdLBin(lambdaBin);
1527           double lambdaCount = 0;
1528           // The FL range sampled for lambda bin [iL*dL .. (iL+1)*dL]
1529           double mindUdLforLambda = 0.0;
1530           double maxdUdLforLambda = 0.0;
1531           double maxBias = 0;
1532           if (firstdUdLBin == -1 || lastdUdLBin == -1) {
1533             ensembleAveragedUdL[lambdaBin] = 0.0;
1534             minFL = 0.0;
1535           } else {
1536             double ensembleAverage = 0.0;
1537             double partitionFunction = 0.0;
1538 
1539             // Evaluate and regularize all kernel values for this value of lambda.
1540             double offset = evaluateKernelForLambda(lambdaBin, firstdUdLBin, lastdUdLBin);
1541 
1542             for (int dUdLBin = firstdUdLBin; dUdLBin <= lastdUdLBin; dUdLBin++) {
1543               double kernel = kernelValues[dUdLBin];
1544 
1545               if (kernel - offset > maxBias) {
1546                 maxBias = kernel - offset;
1547               }
1548 
1549               double weight;
1550               if (biasMagZero) {
1551                 // The weight is just the kernel value for no 2D bias.
1552                 weight = kernel;
1553               } else {
1554                 // The Boltzmann weight based on temperature and the 2D bias height.
1555                 weight = exp(kernel * beta);
1556               }
1557               partitionFunction += weight;
1558 
1559               double currentdUdL = dUdLforBin(dUdLBin);
1560               ensembleAverage += currentdUdL * weight;
1561               lambdaCount += getRecursionKernelValue(lambdaBin, dUdLBin);
1562             }
1563             if (minFL > maxBias) {
1564               minFL = maxBias;
1565             }
1566             ensembleAveragedUdL[lambdaBin] = (partitionFunction == 0) ? 0 : ensembleAverage / partitionFunction;
1567             mindUdLforLambda = hd.dUdLMinimum + firstdUdLBin * hd.dUdLBinWidth;
1568             maxdUdLforLambda = hd.dUdLMinimum + (lastdUdLBin + 1) * hd.dUdLBinWidth;
1569           }
1570 
1571           double deltaFreeEnergy = ensembleAveragedUdL[lambdaBin] * deltaForLambdaBin(lambdaBin);
1572           freeEnergyDifferenceOST += deltaFreeEnergy;
1573           totalWeight += lambdaCount;
1574 
1575           if (print || save) {
1576             double llL = lambdaBin * hd.lambdaBinWidth - hd.lambdaBinWidth_2;
1577             double ulL = llL + hd.lambdaBinWidth;
1578             if (llL < 0.0) {
1579               llL = 0.0;
1580             }
1581             if (ulL > 1.0) {
1582               ulL = 1.0;
1583             }
1584 
1585             double midLambda = llL;
1586             if (!hd.discreteLambda) {
1587               midLambda = (llL + ulL) / 2.0;
1588             }
1589             double bias1D = energyAndGradient1D(midLambda, false);
1590 
1591             double bias2D = 0.0;
1592             if (!biasMagZero) {
1593               bias2D = computeBiasEnergy(midLambda, ensembleAveragedUdL[lambdaBin]) - bias1D;
1594             }
1595 
1596             stringBuilder.append(
1597                 format(" %6.2e %7.5f %7.1f %7.1f %8.2f %8.2f %8.2f %8.2f %8.2f   %8.2f\n", lambdaCount,
1598                     midLambda, mindUdLforLambda, maxdUdLforLambda, ensembleAveragedUdL[lambdaBin],
1599                     bias1D, bias2D, bias1D + bias2D, freeEnergyDifferenceOST, bias1D + bias2D + freeEnergyDifferenceOST));
1600           }
1601         }
1602 
1603         if (!biasMagZero) {
1604           double temperingOffset = hd.getTemperingOffset();
1605           double temperEnergy = (minFL > temperingOffset) ? temperingOffset - minFL : 0;
1606           temperingWeight = exp(temperEnergy / deltaT);
1607         }
1608 
1609         freeEnergyDifferenceOST = integrateNumeric(ensembleAveragedUdL, integrationType);
1610 
1611         if (print) {
1612           logger.info("  Weight   Lambda      dU/dL Bins  <dU/dL>    g(L)  f(L,<dU/dL>) Bias    dG(L) Bias+dG(L)");
1613           logger.info(stringBuilder.toString());
1614           logger.info(" Histogram Evaluation");
1615           logger.info(format("  Free Energy Difference: %12.4f kcal/mol", freeEnergyDifferenceOST));
1616           logger.info(format("  Number of Hills:        %12d", hd.counts));
1617           logger.info(format("  Total Bias Added:       %12.4f kcal/mol", totalWeight * hd.biasMag));
1618           logger.info(format("  Minimum Bias:           %12.4f kcal/mol", minFL));
1619           logger.info(format("  Tempering Percentage:   %12.4f %%\n", temperingWeight * 100));
1620         }
1621 
1622         if (save) {
1623           String modelFilename = molecularAssembly.getFile().getAbsolutePath();
1624           File saveDir = new File(FilenameUtils.getFullPath(modelFilename));
1625           String dirName = saveDir + File.separator;
1626           String fileName = dirName + "histogram.txt";
1627           try {
1628             logger.info("\n Writing " + fileName);
1629             PrintWriter printWriter = new PrintWriter(fileName);
1630             printWriter.write(stringBuilder.toString());
1631             printWriter.close();
1632           } catch (Exception e) {
1633             logger.info(format(" Failed to write %s.", fileName));
1634           }
1635         }
1636 
1637         currentNumberOfHills = hd.counts;
1638         currentFreeEnergyDifference = freeEnergyDifferenceOST;
1639         return currentFreeEnergyDifference;
1640       }
1641     }
1642 
1643     /**
1644      * For thermodynamic integration, return the integration width for the given Lambda lambdaBin.
1645      *
1646      * @param lambdaBin The lambda lambdaBin.
1647      * @return The integration width.
1648      */
1649     private double deltaForLambdaBin(int lambdaBin) {
1650       if (!hd.discreteLambda && (lambdaBin == 0 || lambdaBin == hd.lambdaBins - 1)) {
1651         // The first and last lambda bins are half size for continuous lambda.
1652         return hd.lambdaBinWidth_2;
1653       } else if (hd.discreteLambda && lambdaBin == 0) {
1654         // The free energy change to move from L=0 to L=0 is zero.
1655         return 0.0;
1656       }
1657       // All other cases.
1658       return hd.lambdaBinWidth;
1659     }
1660 
1661     /**
1662      * Returns the index of the first dU/dL bin with counts, or -1 if there are no counts for the
1663      * given lambda bin.
1664      *
1665      * @param lambdaBin Lambda bin to consider.
1666      * @return Index of the first dUdL bin with counts.
1667      */
1668     private int firstdUdLBin(int lambdaBin) {
1669       // Synchronize use of the recursion kernel.
1670       synchronized (this) {
1671         // Find the smallest FL bin that has counts.
1672         for (int jFL = 0; jFL < hd.dUdLBins; jFL++) {
1673           double count = hd.zHistogram[lambdaBin][jFL];
1674           if (count > 0) {
1675             return jFL;
1676           }
1677         }
1678         return -1;
1679       }
1680     }
1681 
1682     /**
1683      * Returns the index of the last dU/dL bin with counts, or -1 if there are no counts for the
1684      * given lambda bin.
1685      *
1686      * @param lambdaBin Lambda bin to consider.
1687      * @return Index of the last dUdL bin with counts.
1688      */
1689     private int lastdUdLBin(int lambdaBin) {
1690       // Synchronize use of the recursion kernel.
1691       synchronized (this) {
1692         // Find the largest FL bin that has counts.
1693         for (int jFL = hd.dUdLBins - 1; jFL >= 0; jFL--) {
1694           double count = hd.zHistogram[lambdaBin][jFL];
1695           if (count > 0) {
1696             return jFL;
1697           }
1698         }
1699         return -1;
1700       }
1701     }
1702 
1703     /**
1704      * Write histogram and lambda restart files.
1705      * <p>
1706      * For MPI parallel jobs, only rank 0 writes a histogram restart file (unless the "independentWrite" flag is set).
1707      */
1708     void writeRestart() {
1709       if (rank == 0 || hd.independentWrite) {
1710         updateFreeEnergyDifference(true, false);
1711         try {
1712           hd.writeHistogram();
1713           logger.info(format(" Wrote histogram restart to: %s.", hd.getHistogramFileName()));
1714         } catch (Exception ex) {
1715           String message = format(" Exception writing histogram restart file %s.", hd.getHistogramFileName());
1716           logger.log(Level.INFO, Utilities.stackTraceToString(ex));
1717           logger.log(Level.SEVERE, message, ex);
1718         }
1719       }
1720 
1721       // All ranks write a lambda restart file.
1722       try {
1723         ld.writeLambdaData();
1724         logger.info(format(" Wrote lambda restart to:    %s.", ld.getLambdaFileName()));
1725       } catch (Exception ex) {
1726         String message = format(" Exception writing lambda restart file %s.", ld.getLambdaFileName());
1727         logger.log(Level.INFO, Utilities.stackTraceToString(ex));
1728         logger.log(Level.SEVERE, message, ex);
1729       }
1730     }
1731 
1732     private double mapLambda(double lambda) {
1733       if (hd.discreteLambda) {
1734         return hd.lambdaLadder[indexForDiscreteLambda(lambda)];
1735       } else {
1736         return lambda;
1737       }
1738     }
1739 
1740     /**
1741      * For continuous lambda, the returned value is the lambda bin. For discrete lambda, the returned
1742      * value is the discrete lambda index.
1743      *
1744      * @param lambda a double.
1745      * @return a int.
1746      */
1747     int indexForLambda(double lambda) {
1748       if (hd.discreteLambda) {
1749         return indexForDiscreteLambda(lambda);
1750       } else {
1751         return indexForContinuousLambda(lambda);
1752       }
1753     }
1754 
1755     int indexForLambda() {
1756       return indexForLambda(ld.lambda);
1757     }
1758 
1759     private double lambdaForIndex(int bin) {
1760       if (hd.discreteLambda) {
1761         return hd.lambdaLadder[bin];
1762       } else {
1763         return bin * hd.lambdaBinWidth;
1764       }
1765     }
1766 
1767     private int indexForContinuousLambda(double lambda) {
1768       int lambdaBin = (int) floor((lambda - hd.minLambda) / hd.lambdaBinWidth);
1769       if (lambdaBin < 0) {
1770         lambdaBin = 0;
1771       }
1772       if (lambdaBin >= hd.lambdaBins) {
1773         lambdaBin = hd.lambdaBins - 1;
1774       }
1775       return lambdaBin;
1776     }
1777 
1778     private int indexForDiscreteLambda(double lambda) {
1779       assert hd.discreteLambda && hd.lambdaLadder != null && hd.lambdaLadder.length > 0;
1780 
1781       int initialGuess = indexForContinuousLambda(lambda);
1782       double minErr = Double.MAX_VALUE;
1783       int minErrBin = -1;
1784       for (int i = -1; i < 2; i++) {
1785         int guessBin = i + initialGuess;
1786         if (guessBin < 0 || guessBin >= hd.lambdaBins) {
1787           continue;
1788         }
1789         double guessLam = hd.lambdaLadder[guessBin];
1790         double guessErr = Math.abs(guessLam - lambda);
1791         if (guessErr < minErr) {
1792           minErr = guessErr;
1793           minErrBin = guessBin;
1794         }
1795       }
1796 
1797       assert minErr < 1.0E-6;
1798       return minErrBin;
1799     }
1800 
1801     /**
1802      * Find the bin for the supplied dEdLambda.
1803      * <p>
1804      * If the supplied dEdL is outside the range of the count matrix, then -1 is returned.
1805      *
1806      * @param dUdL a double.
1807      * @return The dUdL bin.
1808      */
1809     int binFordUdL(double dUdL) {
1810 
1811       // No counts below mindUdL.
1812       if (dUdL < hd.dUdLMinimum) {
1813         return -1;
1814       }
1815 
1816       // No counts above the maxdUdL.
1817       if (dUdL > hd.dUdLMaximum) {
1818         return -1;
1819       }
1820 
1821       int bin = (int) floor((dUdL - hd.dUdLMinimum) / hd.dUdLBinWidth);
1822 
1823       if (bin == hd.dUdLBins) {
1824         bin = hd.dUdLBins - 1;
1825       }
1826 
1827       if (bin < 0) {
1828         bin = 0;
1829       }
1830 
1831       return bin;
1832     }
1833 
1834     /**
1835      * Return the value of a recursion kernel bin. Mirror conditions are applied to the lambda bin.
1836      * If the dUdLBin is outside the range of the count matrix, zero is returned.
1837      *
1838      * @param lambdaBin The lambda bin.
1839      * @param dUdLBin   The dU/dL bin.
1840      * @return The value of the bin.
1841      */
1842     double getRecursionKernelValue(int lambdaBin, int dUdLBin) {
1843       // Synchronize use of the recursion kernel.
1844       synchronized (this) {
1845         // Apply lambda mirror condition.
1846         lambdaBin = lambdaMirror(lambdaBin);
1847 
1848         // For dUdL outside the count matrix the weight is 0.
1849         if (dUdLBin < 0 || dUdLBin >= hd.dUdLBins) {
1850           return 0.0;
1851         }
1852 
1853         return hd.zHistogram[lambdaBin][dUdLBin];
1854       }
1855     }
1856 
1857     /**
1858      * Return the value of a recursion kernel bin. Mirror conditions are applied to the lambda bin.
1859      * If the dUdL is outside the range of the count matrix, zero is returned.
1860      *
1861      * @param lambda the lambda value.
1862      * @param dUdL   the dU/dL value.
1863      * @return The value of the Histogram.
1864      */
1865     double getRecursionKernelValue(double lambda, double dUdL) {
1866       return getRecursionKernelValue(indexForLambda(lambda), binFordUdL(dUdL));
1867     }
1868 
1869     /**
1870      * Add to the value of a recursion kernel bin.
1871      *
1872      * @param currentLambda The lambda bin.
1873      * @param currentdUdL   The dU/dL bin.
1874      * @param value         The value of the bin.
1875      */
1876     void addToRecursionKernelValue(double currentLambda, double currentdUdL, double value) {
1877       synchronized (this) {
1878         if (spreadBias) {
1879           // Expand the recursion kernel if necessary.
1880           int dUdLbiasCutoff = hd.dUdLBiasCutoff;
1881           checkRecursionKernelSize(dUdLforBin(binFordUdL(currentdUdL) - dUdLbiasCutoff));
1882           checkRecursionKernelSize(dUdLforBin(binFordUdL(currentdUdL) + dUdLbiasCutoff));
1883 
1884           int currentLambdaBin = indexForLambda(currentLambda);
1885           int currentdUdLBin = binFordUdL(currentdUdL);
1886 
1887           // Variances are only used when dividing by twice their value.
1888           double invLs2 = 0.5 / hd.lambdaVariance;
1889           double invFLs2 = 0.5 / hd.dUdLVariance;
1890 
1891           // Compute the normalization.
1892           double normalize = 0.0;
1893           int lambdaBiasCutoff = hd.lambdaBiasCutoff;
1894           for (int iL = -lambdaBiasCutoff; iL <= lambdaBiasCutoff; iL++) {
1895             int lambdaBin = currentLambdaBin + iL;
1896             double deltaL = currentLambda - lambdaBin * hd.lambdaBinWidth;
1897             double deltaL2 = deltaL * deltaL;
1898             // Pre-compute the lambda bias.
1899             double L2exp = exp(-deltaL2 * invLs2);
1900             for (int jFL = -dUdLbiasCutoff; jFL <= dUdLbiasCutoff; jFL++) {
1901               int dUdLBin = currentdUdLBin + jFL;
1902               double deltaFL = currentdUdL - dUdLforBin(dUdLBin);
1903               double deltaFL2 = deltaFL * deltaFL;
1904               normalize += L2exp * exp(-deltaFL2 * invFLs2);
1905             }
1906           }
1907 
1908           for (int iL = -lambdaBiasCutoff; iL <= lambdaBiasCutoff; iL++) {
1909             int lambdaBin = currentLambdaBin + iL;
1910             double deltaL = currentLambda - lambdaBin * hd.lambdaBinWidth;
1911             double deltaL2 = deltaL * deltaL;
1912             // Pre-compute the lambda bias.
1913             double L2exp = exp(-deltaL2 * invLs2);
1914             lambdaBin = lambdaMirror(lambdaBin);
1915             for (int jFL = -dUdLbiasCutoff; jFL <= dUdLbiasCutoff; jFL++) {
1916               int dUdLBin = currentdUdLBin + jFL;
1917               double deltaFL = currentdUdL - dUdLforBin(dUdLBin);
1918               double deltaFL2 = deltaFL * deltaFL;
1919               double weight = value / normalize * L2exp * exp(-deltaFL2 * invFLs2);
1920               hd.zHistogram[lambdaBin][dUdLBin] += weight;
1921             }
1922           }
1923           hd.counts++;
1924         } else {
1925           // Check the recursion kernel size.
1926           checkRecursionKernelSize(currentdUdL);
1927           int lambdaBin = indexForLambda(currentLambda);
1928           int dUdLBin = binFordUdL(currentdUdL);
1929           try {
1930             hd.zHistogram[lambdaBin][dUdLBin] += value;
1931             hd.counts++;
1932           } catch (IndexOutOfBoundsException e) {
1933             logger.warning(format(
1934                 " Count skipped in addToRecursionKernelValue due to an index out of bounds exception.\n L=%10.8f (%d), dU/dL=%10.8f (%d) and count=%10.8f",
1935                 currentLambda, lambdaBin, currentdUdL, dUdLBin, value));
1936           }
1937         }
1938       }
1939     }
1940 
1941     /**
1942      * Allocate memory for the recursion kernel.
1943      */
1944     void allocateRecursionKernel() {
1945       // Synchronize updates of the recursion kernel.
1946       synchronized (this) {
1947         hd.zHistogram = new double[hd.lambdaBins][hd.dUdLBins];
1948         kernelValues = new double[hd.dUdLBins];
1949       }
1950     }
1951 
1952     /**
1953      * Integrates dUdL over lambda using more sophisticated techniques than midpoint rectangular
1954      * integration.
1955      *
1956      * <p>The ends (from 0 to dL and 1-dL to 1) are integrated with trapezoids for continuous
1957      * lambda.
1958      *
1959      * @param dUdLs dUdL at the midpoint of each bin.
1960      * @param type  Integration type to use.
1961      * @return Current delta-G estimate.
1962      */
1963     private double integrateNumeric(double[] dUdLs, IntegrationType type) {
1964       double val;
1965       if (hd.discreteLambda) {
1966         // Integrate between the first bin and the last bin.
1967         double[] lams = Integrate1DNumeric.generateXPoints(0.0, 1.0, hd.lambdaBins, false);
1968         DataSet dSet = new DoublesDataSet(lams, dUdLs, false);
1969         val = Integrate1DNumeric.integrateData(dSet, Integrate1DNumeric.IntegrationSide.LEFT, type);
1970       } else {
1971         // Integrate between the second bin midpoint and the second-to-last bin midpoint.
1972         double[] midLams = Integrate1DNumeric.generateXPoints(hd.lambdaBinWidth, 1.0 - hd.lambdaBinWidth,
1973             (hd.lambdaBins - 2), false);
1974         double[] midVals = Arrays.copyOfRange(dUdLs, 1, (hd.lambdaBins - 1));
1975         DataSet dSet = new DoublesDataSet(midLams, midVals, false);
1976 
1977         val = Integrate1DNumeric.integrateData(dSet, Integrate1DNumeric.IntegrationSide.LEFT, type);
1978 
1979         // Everything after this is just adding in the endpoint contributions.
1980 
1981         double dL_4 = hd.lambdaBinWidth_2 * 0.5;
1982 
1983         // Initially, assume dU/dL is exactly 0 at the endpoints. This is sometimes a true
1984         // assumption.
1985         double val0 = 0;
1986         double val1 = 0;
1987 
1988         // If we cannot guarantee that dUdL is exactly 0 at the endpoints, interpolate.
1989         if (!lambdaInterface.dEdLZeroAtEnds()) {
1990           double recipSlopeLen = 1.0 / (hd.lambdaBinWidth * 0.75);
1991 
1992           double slope = dUdLs[0] - dUdLs[1];
1993           slope *= recipSlopeLen;
1994           val0 = dUdLs[0] + (slope * dL_4);
1995 
1996           slope = dUdLs[hd.lambdaBins - 1] - dUdLs[hd.lambdaBins - 2];
1997           slope *= recipSlopeLen;
1998           val1 = dUdLs[hd.lambdaBins - 1] + (slope * dL_4);
1999           logger.fine(format(" Inferred dU/dL values at 0 and 1: %10.5g , %10.5g", val0, val1));
2000         }
2001 
2002         // Integrate trapezoids from 0 to the second bin midpoint, and from second-to-last bin
2003         // midpoint to 1.
2004         val += trapezoid(0, dL_4, val0, dUdLs[0]);
2005         val += trapezoid(dL_4, hd.lambdaBinWidth, dUdLs[0], dUdLs[1]);
2006         val += trapezoid(1.0 - hd.lambdaBinWidth, 1.0 - dL_4, dUdLs[hd.lambdaBins - 2],
2007             dUdLs[hd.lambdaBins - 1]);
2008         val += trapezoid(1.0 - dL_4, 1.0, dUdLs[hd.lambdaBins - 1], val1);
2009       }
2010 
2011       return val;
2012     }
2013 
2014     /**
2015      * Integrates a trapezoid.
2016      *
2017      * @param x0  First x point
2018      * @param x1  Second x point
2019      * @param fx0 First f(x) point
2020      * @param fx1 Second f(x) point
2021      * @return The area under a trapezoid.
2022      */
2023     private double trapezoid(double x0, double x1, double fx0, double fx1) {
2024       double val = 0.5 * (fx0 + fx1);
2025       val *= (x1 - x0);
2026       return val;
2027     }
2028 
2029     /**
2030      * Evaluate the kernel across dU/dL values at given value of lambda. The largest kernel value V
2031      * is used to define an offset (-V), which is added to all to kernel values. Then, the largest
2032      * kernel value is zero, and will result in a statistical weight of 1.
2033      *
2034      * <p>The offset avoids the recursion kernel becoming too large for some combinations of
2035      * (Lambda, dU/dL). This can result in its statistical weight = exp(kernel * beta) exceeding the
2036      * maximum representable double value.
2037      *
2038      * @param lambda Value of Lambda to evaluate the kernel for.
2039      * @param llFL   Lower FLambda bin.
2040      * @param ulFL   Upper FLambda bin.
2041      * @return the applied offset.
2042      */
2043     private double evaluateKernelForLambda(int lambda, int llFL, int ulFL) {
2044       double maxKernel = Double.MIN_VALUE;
2045 
2046       double gaussianBiasMagnitude = hd.biasMag;
2047       if (hd.biasMag <= 0.0) {
2048         gaussianBiasMagnitude = PSEUDO_BIAS_MAGNITUDE;
2049       }
2050 
2051       for (int jFL = llFL; jFL <= ulFL; jFL++) {
2052         double value = evaluateKernel(lambda, jFL, gaussianBiasMagnitude);
2053         kernelValues[jFL] = value;
2054         if (value > maxKernel) {
2055           maxKernel = value;
2056         }
2057       }
2058 
2059       // Only offset the kernel values for use with the 2D OST bias.
2060       double offset = 0.0;
2061       if (hd.biasMag > 0.0 && !hd.metaDynamics) {
2062         offset = -maxKernel;
2063         for (int jFL = llFL; jFL <= ulFL; jFL++) {
2064           kernelValues[jFL] += offset;
2065         }
2066       }
2067       return offset;
2068     }
2069 
2070     /**
2071      * Mirror the lambda bin if its less < 0 or greater than the last bin.
2072      *
2073      * @param bin Lambda bin to mirror.
2074      * @return The mirrored lambda bin.
2075      */
2076     private int lambdaMirror(int bin) {
2077       if (bin < 0) {
2078         return -bin;
2079       }
2080       int lastBin = hd.lambdaBins - 1;
2081       if (bin > lastBin) {
2082         // Number of bins past the last bin.
2083         bin -= lastBin;
2084         // Return Mirror bin
2085         return lastBin - bin;
2086       }
2087       // No mirror condition.
2088       return bin;
2089     }
2090 
2091     /**
2092      * For continuous lambda, the width of the first and last bins is dLambda_2, so the mirror
2093      * condition is to double their counts.
2094      *
2095      * @param bin Current lambda bin.
2096      * @return The mirror factor (either 1.0 or 2.0).
2097      */
2098     private double mirrorFactor(int bin) {
2099       if (hd.discreteLambda) {
2100         return 1.0;
2101       }
2102       if (bin == 0 || bin == hd.lambdaBins - 1) {
2103         return 2.0;
2104       }
2105       return 1.0;
2106     }
2107 
2108     /**
2109      * Compute the value of dU/dL for the given Histogram bin.
2110      *
2111      * @param dUdLBin The bin index in the dU/dL dimension.
2112      * @return The value of dU/dL at the center of the bin.
2113      */
2114     private double dUdLforBin(int dUdLBin) {
2115       return hd.dUdLMinimum + dUdLBin * hd.dUdLBinWidth + hd.dUdLBinWidth_2;
2116     }
2117 
2118     /**
2119      * Evaluate the bias at [currentLambdaBin, cF_lambda]
2120      */
2121     private double evaluateKernel(int currentLambdaBin, int currentdUdLBin, double gaussianBiasMagnitude) {
2122       // Compute the value of L and FL for the center of the current bin.
2123       double currentLambda = currentLambdaBin * hd.lambdaBinWidth;
2124       double currentdUdL = dUdLforBin(currentdUdLBin);
2125 
2126       // Variances are only used when dividing by twice their value.
2127       double invLs2 = 0.5 / hd.lambdaVariance;
2128       double invFLs2 = 0.5 / hd.dUdLVariance;
2129 
2130       double sum = 0.0;
2131       int lambdaBiasCutoff = hd.lambdaBiasCutoff;
2132       for (int iL = -lambdaBiasCutoff; iL <= lambdaBiasCutoff; iL++) {
2133         int lambdaBin = currentLambdaBin + iL;
2134         double deltaL = currentLambda - lambdaBin * hd.lambdaBinWidth;
2135         double deltaL2 = deltaL * deltaL;
2136 
2137         // Pre-compute the lambda bias.
2138         double L2exp = exp(-deltaL2 * invLs2);
2139 
2140         // Mirror condition for Lambda counts.
2141         lambdaBin = lambdaMirror(lambdaBin);
2142         double mirrorFactor = mirrorFactor(lambdaBin);
2143 
2144         int dUdLbiasCutoff = hd.dUdLBiasCutoff;
2145         for (int jFL = -dUdLbiasCutoff; jFL <= dUdLbiasCutoff; jFL++) {
2146           int dUdLBin = currentdUdLBin + jFL;
2147           double weight = mirrorFactor * getRecursionKernelValue(lambdaBin, dUdLBin);
2148           if (weight <= 0.0) {
2149             continue;
2150           }
2151           double deltaFL = currentdUdL - dUdLforBin(dUdLBin);
2152           double deltaFL2 = deltaFL * deltaFL;
2153           double e = weight * L2exp * exp(-deltaFL2 * invFLs2);
2154           sum += e;
2155         }
2156       }
2157       return gaussianBiasMagnitude * sum;
2158     }
2159 
2160     /**
2161      * Compute the total Bias energy at (currentLambda, currentdUdL).
2162      *
2163      * @param currentLambda The value of lambda.
2164      * @param currentdUdL   The value of dU/dL.
2165      * @return The bias energy.
2166      */
2167     double computeBiasEnergy(double currentLambda, double currentdUdL) {
2168       synchronized (this) {
2169         int currentLambdaBin = indexForLambda(currentLambda);
2170         int currentdUdLBin = binFordUdL(currentdUdL);
2171 
2172         double bias1D;
2173         double bias2D = 0.0;
2174 
2175         if (!hd.metaDynamics) {
2176           if (hd.biasMag > 0.0) {
2177             int lambdaBiasCutoff = hd.lambdaBiasCutoff;
2178             for (int iL = -lambdaBiasCutoff; iL <= lambdaBiasCutoff; iL++) {
2179 
2180               int lambdaBin = currentLambdaBin + iL;
2181               double deltaL = currentLambda - (lambdaBin * hd.lambdaBinWidth);
2182               double deltaL2 = deltaL * deltaL;
2183               double expL2 = exp(-deltaL2 / (2.0 * hd.lambdaVariance));
2184 
2185               // Mirror conditions for recursion kernel counts.
2186               lambdaBin = lambdaMirror(lambdaBin);
2187               double mirrorFactor = mirrorFactor(lambdaBin);
2188 
2189               int dUdLbiasCutoff = hd.dUdLBiasCutoff;
2190               for (int iFL = -dUdLbiasCutoff; iFL <= dUdLbiasCutoff; iFL++) {
2191                 int dUdLBin = currentdUdLBin + iFL;
2192 
2193                 double weight = mirrorFactor * getRecursionKernelValue(lambdaBin, dUdLBin);
2194                 if (weight <= 0.0) {
2195                   continue;
2196                 }
2197 
2198                 double deltaFL = currentdUdL - dUdLforBin(dUdLBin);
2199                 double deltaFL2 = deltaFL * deltaFL;
2200                 double bias = weight * hd.biasMag * expL2 * exp(-deltaFL2 / (2.0 * hd.dUdLVariance));
2201                 bias2D += bias;
2202               }
2203             }
2204           }
2205           // Compute the energy for the recursion worker at F(L) using interpolation.
2206           bias1D = energyAndGradient1D(currentLambda, false);
2207         } else {
2208           bias1D = energyAndGradientMeta(currentLambda, false);
2209         }
2210 
2211         // Return the total bias.
2212         return bias1D + bias2D;
2213       }
2214     }
2215 
2216     /**
2217      * Compute the total Bias energy at (currentLambda, currentdUdL) and its gradient.
2218      *
2219      * @param currentdUdLambda The value of dU/dL.
2220      * @param chainRule        The chain rule terms for the bias.
2221      * @return The bias energy.
2222      */
2223     double energyAndGradient2D(double currentdUdLambda, double[] chainRule) {
2224       return energyAndGradient2D(ld.lambda, currentdUdLambda, chainRule, hd.biasMag);
2225     }
2226 
2227     /**
2228      * Compute the total Bias energy at (currentLambda, currentdUdL) and its gradient.
2229      *
2230      * @param currentLambda         The value of lambda.
2231      * @param currentdUdLambda      The value of dU/dL.
2232      * @param chainRule             The chain rule terms for the bias.
2233      * @param gaussianBiasMagnitude The magnitude of the Gaussian bias.
2234      * @return The bias energy.
2235      */
2236     double energyAndGradient2D(double currentLambda, double currentdUdLambda, double[] chainRule,
2237                                double gaussianBiasMagnitude) {
2238 
2239       if (gaussianBiasMagnitude <= 0.0) {
2240         chainRule[0] = 0.0;
2241         chainRule[1] = 0.0;
2242         return 0;
2243       }
2244 
2245       // Calculate recursion kernel G(L, F_L) and its derivatives with respect to L and F_L.
2246       double gLdEdL = 0.0;
2247       double dGdLambda = 0.0;
2248       double dGdFLambda = 0.0;
2249       int currentLambdaBin = indexForLambda(currentLambda);
2250       int currentdUdLBin = binFordUdL(currentdUdLambda);
2251 
2252       int lambdaBiasCutoff = hd.lambdaBiasCutoff;
2253       for (int iL = -lambdaBiasCutoff; iL <= lambdaBiasCutoff; iL++) {
2254         int lambdaBin = currentLambdaBin + iL;
2255 
2256         // Pass in the bin offset to the weight instance.
2257         double deltaL = currentLambda - (lambdaBin * hd.lambdaBinWidth);
2258         double deltaL2 = deltaL * deltaL;
2259         double expL2 = exp(-deltaL2 / (2.0 * hd.lambdaVariance));
2260 
2261         // Mirror conditions for recursion kernel counts.
2262         double mirrorFactor = mirrorFactor(lambdaBin);
2263         int dUdLbiasCutoff = hd.dUdLBiasCutoff;
2264         for (int iFL = -dUdLbiasCutoff; iFL <= dUdLbiasCutoff; iFL++) {
2265           int dUdLBin = currentdUdLBin + iFL;
2266 
2267           double weight = mirrorFactor * getRecursionKernelValue(lambdaBin, dUdLBin);
2268           if (weight <= 0.0) {
2269             continue;
2270           }
2271 
2272           double deltaFL = currentdUdLambda - dUdLforBin(dUdLBin);
2273           double deltaFL2 = deltaFL * deltaFL;
2274           double bias = weight * gaussianBiasMagnitude * expL2 * exp(-deltaFL2 / (2.0 * hd.dUdLVariance));
2275           gLdEdL += bias;
2276           dGdLambda -= deltaL / hd.lambdaVariance * bias;
2277           dGdFLambda -= deltaFL / hd.dUdLVariance * bias;
2278         }
2279       }
2280 
2281       chainRule[0] = dGdLambda;
2282       chainRule[1] = dGdFLambda;
2283       return gLdEdL;
2284     }
2285 
2286 
2287     /**
2288      * This calculates the 1D OST bias and its derivative with respect to Lambda.
2289      * <p>
2290      * See Equation 8 in <a href="http://doi.org/10.1021/ct300035u">
2291      * The Structure, Thermodynamics, and Solubility of Organic Crystals from Simulation with a
2292      * Polarizable Force Field</a>.
2293      * <p>
2294      *
2295      * @param gradient If true, compute the gradient.
2296      * @return a double.
2297      */
2298     private double energyAndGradient1D(boolean gradient) {
2299       return energyAndGradient1D(ld.lambda, gradient);
2300     }
2301 
2302     /**
2303      * This calculates the 1D OST bias and its derivative with respect to Lambda.
2304      * <p>
2305      * See Equation 8 in <a href="http://doi.org/10.1021/ct300035u">
2306      * The Structure, Thermodynamics, and Solubility of Organic Crystals from Simulation with a
2307      * Polarizable Force Field</a>.
2308      * <p>
2309      *
2310      * @param currentLambda The current value of lambda.
2311      * @param gradient      If true, compute the gradient.
2312      * @return a double.
2313      */
2314     private double energyAndGradient1D(double currentLambda, boolean gradient) {
2315       synchronized (this) {
2316         double biasEnergy = 0.0;
2317         for (int iL0 = 0; iL0 < hd.lambdaBins - 1; iL0++) {
2318           int iL1 = iL0 + 1;
2319 
2320           // Find bin centers and values for interpolation / extrapolation points.
2321           double L0 = iL0 * hd.lambdaBinWidth;
2322           double L1 = L0 + hd.lambdaBinWidth;
2323           double FL0 = ensembleAveragedUdL[iL0];
2324           double FL1 = ensembleAveragedUdL[iL1];
2325           double deltaFL = FL1 - FL0;
2326           /*
2327            If the lambda is less than or equal to the upper limit, this is
2328            the final interval. Set the upper limit to L, compute the partial
2329            derivative and break.
2330           */
2331           boolean done = false;
2332           if (currentLambda <= L1) {
2333             done = true;
2334             L1 = currentLambda;
2335           }
2336 
2337           // Upper limit - lower limit of the integral of the extrapolation / interpolation.
2338           biasEnergy += (FL0 * L1 + deltaFL * L1 * (0.5 * L1 - L0) / hd.lambdaBinWidth);
2339           biasEnergy -= (FL0 * L0 + deltaFL * L0 * (-0.5 * L0) / hd.lambdaBinWidth);
2340           if (done) {
2341             // Compute the gradient d F(L) / dL at L.
2342             if (gradient) {
2343               dUdLambda -= FL0 + (L1 - L0) * deltaFL / hd.lambdaBinWidth;
2344             }
2345             break;
2346           }
2347         }
2348         return -biasEnergy;
2349       }
2350     }
2351 
2352     /**
2353      * This calculates the 1D Metadynamics bias and its derivative with respect to Lambda.
2354      *
2355      * @param gradient If true, compute the gradient.
2356      * @return a double.
2357      */
2358     private double energyAndGradientMeta(boolean gradient) {
2359       return energyAndGradientMeta(ld.lambda, gradient);
2360     }
2361 
2362     /**
2363      * This calculates the 1D Metadynamics bias and its derivative with respect to Lambda.
2364      *
2365      * @param currentLambda The current value of lambda.
2366      * @param gradient      If true, compute the gradient.
2367      * @return a double.
2368      */
2369     private double energyAndGradientMeta(double currentLambda, boolean gradient) {
2370       // Zero out the metadynamics bias energy.
2371       double biasEnergy = 0.0;
2372 
2373       // Current lambda bin.
2374       int currentBin = indexForLambda(currentLambda);
2375 
2376       // Loop over bins within the cutoff.
2377       int lambdaBiasCutoff = hd.lambdaBiasCutoff;
2378       for (int iL = -lambdaBiasCutoff; iL <= lambdaBiasCutoff; iL++) {
2379         int lambdaBin = currentBin + iL;
2380         double deltaL = currentLambda - (lambdaBin * hd.lambdaBinWidth);
2381         double deltaL2 = deltaL * deltaL;
2382         // Mirror conditions for the lambda bin and count magnitude.
2383         lambdaBin = lambdaMirror(lambdaBin);
2384         double mirrorFactor = mirrorFactor(lambdaBin);
2385         double weight = mirrorFactor * hd.biasMag * countsForLambda(lambdaBin);
2386         if (weight > 0) {
2387           double e = weight * exp(-deltaL2 / (2.0 * hd.lambdaVariance));
2388           biasEnergy += e;
2389           // Add the dMeta/dL contribution.
2390           if (gradient) {
2391             dUdLambda -= deltaL / hd.lambdaVariance * e;
2392           }
2393         }
2394       }
2395 
2396       return biasEnergy;
2397     }
2398 
2399     /**
2400      * Marginalize over dU/dL counts for the given lambda bin.
2401      *
2402      * @param lambdaBin Lambda bin to marginalize for.
2403      * @return Total number of counts.
2404      */
2405     private double countsForLambda(int lambdaBin) {
2406       synchronized (this) {
2407         double count = 0.0;
2408         for (int i = 0; i < hd.dUdLBins; i++) {
2409           count += hd.zHistogram[lambdaBin][i];
2410         }
2411         return count;
2412       }
2413     }
2414 
2415     /**
2416      * If necessary, allocate more space.
2417      */
2418     void checkRecursionKernelSize(double currentdUdL) {
2419       // Synchronize updates of the recursion kernel.
2420       synchronized (this) {
2421         if (currentdUdL > hd.dUdLMaximum) {
2422           logger.info(format(" Current F_lambda %8.2f > maximum histogram size %8.2f.", currentdUdL, hd.dUdLMaximum));
2423 
2424           double origDeltaG = updateFreeEnergyDifference(false, false);
2425 
2426           int newdUdLBins = hd.dUdLBins;
2427           while (hd.dUdLMinimum + newdUdLBins * hd.dUdLBinWidth < currentdUdL) {
2428             newdUdLBins += 100;
2429           }
2430           double[][] newRecursionKernel = new double[hd.lambdaBins][newdUdLBins];
2431 
2432           // We have added bins above the indices of the current counts just copy them into the new array.
2433           for (int i = 0; i < hd.lambdaBins; i++) {
2434             arraycopy(hd.zHistogram[i], 0, newRecursionKernel[i], 0, hd.dUdLBins);
2435           }
2436 
2437           hd.zHistogram = newRecursionKernel;
2438           hd.dUdLBins = newdUdLBins;
2439           kernelValues = new double[hd.dUdLBins];
2440           hd.dUdLMaximum = hd.dUdLMinimum + hd.dUdLBinWidth * hd.dUdLBins;
2441           logger.info(format(" New histogram %8.2f to %8.2f with %d bins.\n", hd.dUdLMinimum, hd.dUdLMaximum, hd.dUdLBins));
2442 
2443           double newFreeEnergy = updateFreeEnergyDifference(true, false);
2444           assert (origDeltaG == newFreeEnergy);
2445         }
2446         if (currentdUdL < hd.dUdLMinimum) {
2447           logger.info(format(" Current F_lambda %8.2f < minimum histogram size %8.2f.", currentdUdL, hd.dUdLMinimum));
2448 
2449           double origDeltaG = updateFreeEnergyDifference(false, false);
2450 
2451           int offset = 100;
2452           while (currentdUdL < hd.dUdLMinimum - offset * hd.dUdLBinWidth) {
2453             offset += 100;
2454           }
2455           int newdUdLBins = hd.dUdLBins + offset;
2456           double[][] newRecursionKernel = new double[hd.lambdaBins][newdUdLBins];
2457 
2458           // We have added bins below the current counts,
2459           // so their indices must be increased by: offset = newFLBins - FLBins
2460           for (int i = 0; i < hd.lambdaBins; i++) {
2461             arraycopy(hd.zHistogram[i], 0, newRecursionKernel[i], offset, hd.dUdLBins);
2462           }
2463 
2464           hd.zHistogram = newRecursionKernel;
2465           hd.dUdLMinimum = hd.dUdLMinimum - offset * hd.dUdLBinWidth;
2466           hd.dUdLBins = newdUdLBins;
2467           kernelValues = new double[hd.dUdLBins];
2468           logger.info(format(" New histogram %8.2f to %8.2f with %d bins.\n", hd.dUdLMinimum, hd.dUdLMaximum, hd.dUdLBins));
2469 
2470           double newFreeEnergy = updateFreeEnergyDifference(true, false);
2471           assert (origDeltaG == newFreeEnergy);
2472         }
2473       }
2474     }
2475 
2476     /**
2477      * Add a Gaussian hill to the Histogram at (lambda, dUdL).
2478      *
2479      * @param dUdL The value of dU/dL.
2480      */
2481     void addBias(double dUdL) {
2482       // Communicate adding the bias to all walkers.
2483       if (hd.asynchronous) {
2484         sendAsynchronous.send(ld.lambda, dUdL, temperingWeight);
2485       } else {
2486         sendSynchronous.send(ld.lambda, dUdL, temperingWeight);
2487       }
2488     }
2489 
2490     /**
2491      * Pre-force portion of the stochastic integrator.
2492      */
2493     private void stochasticPreForce() {
2494       // Update theta.
2495       double[] thetaPosition = lambdaState.x();
2496       thetaPosition[0] = ld.theta;
2497       stochastic.preForce(OrthogonalSpaceTempering.this);
2498       ld.theta = thetaPosition[0];
2499 
2500       // Maintain theta in the interval -PI to PI.
2501       if (ld.theta > PI) {
2502         ld.theta -= 2.0 * PI;
2503       } else if (ld.theta <= -PI) {
2504         ld.theta += 2.0 * PI;
2505       }
2506 
2507       // Update lambda as sin(theta)^2.
2508       double sinTheta = sin(ld.theta);
2509       ld.lambda = sinTheta * sinTheta;
2510       lambdaInterface.setLambda(ld.lambda);
2511     }
2512 
2513     /**
2514      * Post-force portion of the stochastic integrator.
2515      */
2516     private void stochasticPostForce() {
2517       double[] thetaPosition = lambdaState.x();
2518       double[] gradient = lambdaState.gradient();
2519       // Update theta and dU/dtheta.
2520       gradient[0] = dUdLambda * sin(2 * ld.theta);
2521       thetaPosition[0] = ld.theta;
2522       stochastic.postForce(gradient);
2523 
2524       // Load the new theta, velocity and acceleration.
2525       double[] thetaVelocity = lambdaState.v();
2526       double[] thetaAcceleration = lambdaState.a();
2527       ld.theta = thetaPosition[0];
2528       ld.thetaVelocity = thetaVelocity[0];
2529       ld.thetaAcceleration = thetaAcceleration[0];
2530 
2531       // Maintain theta in the interval -PI to PI.
2532       if (ld.theta > PI) {
2533         ld.theta -= 2.0 * PI;
2534       } else if (ld.theta <= -PI) {
2535         ld.theta += 2.0 * PI;
2536       }
2537 
2538       // Update lambda as sin(theta)^2.
2539       double sinTheta = sin(ld.theta);
2540       ld.lambda = sinTheta * sinTheta;
2541       lambdaInterface.setLambda(ld.lambda);
2542     }
2543 
2544     /**
2545      * Gets the last lambda value received by this Histogram. This can be out-of-date w.r.t. the
2546      * OST's current lambda!
2547      *
2548      * @return Lambda value of the last bias added to this Histogram.
2549      */
2550     double getLastReceivedLambda() {
2551       return lastReceivedLambda;
2552     }
2553 
2554     public void setLastReceivedLambda(double lastReceivedLambda) {
2555       this.lastReceivedLambda = lastReceivedLambda;
2556     }
2557 
2558     /**
2559      * Gets the last dU/dL value received by this Histogram. This can be out-of-date w.r.t. the OST's
2560      * current dU/dL!
2561      *
2562      * @return dU/dL value of the last bias added to this Histogram.
2563      */
2564     double getLastReceivedDUDL() {
2565       return lastReceiveddUdL;
2566     }
2567 
2568     void destroy() {
2569       if (sendAsynchronous != null && sendAsynchronous.isAlive()) {
2570         double[] killMessage = new double[]{Double.NaN, Double.NaN, Double.NaN, Double.NaN};
2571         DoubleBuf killBuf = DoubleBuf.buffer(killMessage);
2572         try {
2573           logger.fine(" Sending the termination message.");
2574           world.send(rank, killBuf);
2575           logger.fine(" Termination message was sent successfully.");
2576           logger.fine(format(" Receive thread alive %b status %s", sendAsynchronous.isAlive(),
2577               sendAsynchronous.getState()));
2578         } catch (Exception ex) {
2579           String message = format(" Asynchronous Multiwalker OST termination signal "
2580               + "failed to be sent for process %d.", rank);
2581           logger.log(Level.SEVERE, message, ex);
2582         }
2583       } else {
2584         logger.fine(
2585             " CountReceiveThread was either not initialized, or is not alive. This is the case for the Histogram script.");
2586       }
2587     }
2588 
2589   }
2590 
2591 }