Correct AWH initial histogram size
authorBerk Hess <hess@kth.se>
Wed, 28 Oct 2020 10:47:49 +0000 (10:47 +0000)
committerPaul Bauer <paul.bauer.q@gmail.com>
Wed, 28 Oct 2020 10:47:49 +0000 (10:47 +0000)
The initial histogram size for AWH incorrectly contained
a Gaussian geometry factor and also treated higher dimensional
cases incorrectly. The formula has now been simplified and
described more accurately in the manual.

Fixes #3751

docs/reference-manual/special/awh.rst
docs/release-notes/2021/major/bugs-fixed.rst
src/gromacs/applied_forces/awh/biasparams.cpp
src/gromacs/applied_forces/awh/tests/bias.cpp
src/gromacs/applied_forces/awh/tests/bias_fep_lambda_state.cpp

index faa9f4197900011b55c3b95d91407b26efdaea0c..f5b2fbf225608561a66d6e6aecbf94d2b4a20315 100644 (file)
@@ -602,10 +602,16 @@ free energy scales as :math:`\varepsilon^2 \sim 1/(ND)`
 estimate used by AWH to initialize :math:`N` in terms of more meaningful
 quantities
 
-.. math:: \frac{1}{N_0} = \frac{1}{N_0(\varepsilon_0, D)} \sim D\varepsilon_0^2.
+.. math:: \frac{1}{N_0} = \frac{1}{N_0(\varepsilon_0, D)} = \frac{1}{\Delta
+         t_\mathrm{sample}} \max_d \frac{L_d^2}{2D_d} \varepsilon_0^2
           :label: eqawhn0
 
-Essentially, this tells us that a slower system (small :math:`D`)
+where :math:`L_d` is the length of the interval and :math:`D_d` is
+the diffusion constant along dimension :math:`d` of the AWH bias.
+For one dimension, :math:`L^2/2D` is the average time to diffuse
+over a distance of :math:`L`. We then takes the maximum crossing
+time over all dimensions involved in the bias.
+Essentially, this formula tells us that a slower system (small :math:`D`)
 requires more samples (larger :math:`N^0`) to attain the same level of
 accuracy (:math:`\varepsilon_0`) at a given sampling rate. Conversely,
 for a system of given diffusion, how to choose the initial biasing rate
@@ -621,9 +627,10 @@ run a short trial simulation and after the first covering check the
 maximum free energy difference of the PMF estimate. If this is much
 larger than the expected magnitude of the free energy barriers that
 should be crossed, then the system is probably being pulled too hard and
-:math:`D` should be decreased. :math:`\varepsilon_0` on the other hand,
-would only be tweaked when starting an AWH simulation using a fairly
-accurate guess of the PMF as input.
+:math:`D` should be decreased. An accurate estimate of the diffusion
+can be obtaining from an AWH simulation with the :ref:`gmx awh` tool.
+:math:`\varepsilon_0` on the other hand, should be a rough estimate
+of the initial error.
 
 Tips for efficient sampling
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
index 5d1551ae540096dcfb8b20aace3f20c7cdcf177d..cd2f2c78b33bd140697889ff11b18d8a7e18c99c 100644 (file)
@@ -24,3 +24,16 @@ and writing pdb files. This affected naming of
 H atoms in particular.
 
 :issue:`3469`
+
+Corrected AWH initial histogram size
+""""""""""""""""""""""""""""""""""""
+
+The initial histogram size for AWH biases depended (weakly) on the force
+constant. This dependence has been removed, which increases the histogram
+size by a about a factor of 3. In practice this has only a minor effect
+on the time to solution. For multiple dimensions, the histogram size was
+underestimated, in particular with a combination of slower and faster
+dimensions. The, now simplified, formula for the initial histogram size is
+given in the reference manual.
+
+:issue:`3751`
index 96f1b5e67cdbb72420e3d347d5c7c5e0082e0fe4..182713007747a0f5f87954939993af84fac69d02 100644 (file)
@@ -165,123 +165,34 @@ int64_t calcCheckCoveringInterval(const AwhParams&              awhParams,
     return numStepsCheck;
 }
 
-/*! \brief
- * Returns an approximation of the geometry factor used for initializing the AWH update size.
- *
- * The geometry factor is defined as the following sum of Gaussians:
- * sum_{k!=0} exp(-0.5*(k*pi*x)^2)/(pi*k)^2,
- * where k is a xArray.size()-dimensional integer vector with k_i in {0,1,..}.
- *
- * \param[in] xArray  Array to evaluate.
- * \returns the geometry factor.
- */
-double gaussianGeometryFactor(gmx::ArrayRef<const double> xArray)
-{
-    /* For convenience we give the geometry factor function a name: zeta(x) */
-    constexpr size_t                    tableSize  = 5;
-    std::array<const double, tableSize> xTabulated = { { 1e-5, 1e-4, 1e-3, 1e-2, 1e-1 } };
-    std::array<const double, tableSize> zetaTable1d = { { 0.166536811948, 0.16653116886, 0.166250075882,
-                                                          0.162701098306, 0.129272430287 } };
-    std::array<const double, tableSize> zetaTable2d = { { 2.31985974274, 1.86307292523, 1.38159772648,
-                                                          0.897554759158, 0.405578211115 } };
-
-    gmx::ArrayRef<const double> zetaTable;
-
-    if (xArray.size() == 1)
-    {
-        zetaTable = zetaTable1d;
-    }
-    else if (xArray.size() == 2)
-    { // NOLINT bugprone-branch-clone
-        zetaTable = zetaTable2d;
-    }
-    else
-    {
-        /* TODO... but this is anyway a rough estimate and > 2 dimensions is not so popular.
-         * Remove the above NOLINT when addressing this */
-        zetaTable = zetaTable2d;
-    }
-
-    /* TODO. Really zeta is a function of an ndim-dimensional vector x and we shoudl have a ndim-dimensional lookup-table.
-       Here we take the geometric average of the components of x which is ok if the x-components are not very different. */
-    double xScalar = 1;
-    for (const double& x : xArray)
-    {
-        xScalar *= x;
-    }
-
-    GMX_ASSERT(!xArray.empty(), "We should have a non-empty input array");
-    xScalar = std::pow(xScalar, 1.0 / xArray.size());
-
-    /* Look up zeta(x) */
-    size_t xIndex = 0;
-    while ((xIndex < xTabulated.size()) && (xScalar > xTabulated[xIndex]))
-    {
-        xIndex++;
-    }
-
-    double zEstimate;
-    if (xIndex == xTabulated.size())
-    {
-        /* Take last value */
-        zEstimate = zetaTable[xTabulated.size() - 1];
-    }
-    else if (xIndex == 0)
-    {
-        zEstimate = zetaTable[xIndex];
-    }
-    else
-    {
-        /* Interpolate */
-        double x0 = xTabulated[xIndex - 1];
-        double x1 = xTabulated[xIndex];
-        double w  = (xScalar - x0) / (x1 - x0);
-        zEstimate = w * zetaTable[xIndex - 1] + (1 - w) * zetaTable[xIndex];
-    }
-
-    return zEstimate;
-}
-
 /*! \brief
  * Estimate a reasonable initial reference weight histogram size.
  *
- * \param[in] dimParams         Parameters for the dimensions of the coordinate.
  * \param[in] awhBiasParams     Bias parameters.
  * \param[in] gridAxis          The BiasGrid axes.
  * \param[in] beta              1/(k_B T).
  * \param[in] samplingTimestep  Sampling frequency of probability weights.
  * \returns estimate of initial histogram size.
  */
-double getInitialHistogramSizeEstimate(const std::vector<DimParams>& dimParams,
-                                       const AwhBiasParams&          awhBiasParams,
-                                       const std::vector<GridAxis>&  gridAxis,
-                                       double                        beta,
-                                       double                        samplingTimestep)
+double getInitialHistogramSizeEstimate(const AwhBiasParams&         awhBiasParams,
+                                       const std::vector<GridAxis>& gridAxis,
+                                       double                       beta,
+                                       double                       samplingTimestep)
 {
     /* Get diffusion factor */
-    double              crossingTime = 0.;
+    double              maxCrossingTime = 0.;
     std::vector<double> x;
     for (size_t d = 0; d < gridAxis.size(); d++)
     {
-        double axisLength = gridAxis[d].isFepLambdaAxis() ? 1.0 : gridAxis[d].length();
-        if (axisLength > 0)
-        {
-            crossingTime += awhBiasParams.dimParams[d].diffusion / (axisLength * axisLength);
-            /* The sigma of the Gaussian distribution in the umbrella */
-            double sigma = 1.;
-            if (dimParams[d].isPullDimension())
-            {
-                GMX_RELEASE_ASSERT(dimParams[d].pullDimParams().betak != 0,
-                                   "beta*k cannot be zero");
-                sigma /= std::sqrt(dimParams[d].pullDimParams().betak);
-            }
-            x.push_back(sigma / axisLength);
-        }
+        GMX_RELEASE_ASSERT(awhBiasParams.dimParams[d].diffusion > 0, "We need positive diffusion");
+        // With diffusion it takes on average T = L^2/2D time to cross length L
+        double axisLength   = gridAxis[d].isFepLambdaAxis() ? 1.0 : gridAxis[d].length();
+        double crossingTime = (axisLength * axisLength) / (2 * awhBiasParams.dimParams[d].diffusion);
+        maxCrossingTime     = std::max(maxCrossingTime, crossingTime);
     }
-    GMX_RELEASE_ASSERT(crossingTime > 0, "We need at least one dimension with non-zero length");
+    GMX_RELEASE_ASSERT(maxCrossingTime > 0, "We need at least one dimension with non-zero length");
     double errorInitialInKT = beta * awhBiasParams.errorInitial;
-    double histogramSize    = gaussianGeometryFactor(x)
-                           / (crossingTime * gmx::square(errorInitialInKT) * samplingTimestep);
+    double histogramSize    = maxCrossingTime / (gmx::square(errorInitialInKT) * samplingTimestep);
 
     return histogramSize;
 }
@@ -332,11 +243,8 @@ BiasParams::BiasParams(const AwhParams&              awhParams,
     updateWeight(numSamplesUpdateFreeEnergy_ * numSharedUpdate),
     localWeightScaling(eTarget == eawhtargetLOCALBOLTZMANN ? temperatureScaleFactor : 1),
     initialErrorInKT(beta * awhBiasParams.errorInitial),
-    initialHistogramSize(getInitialHistogramSizeEstimate(dimParams,
-                                                         awhBiasParams,
-                                                         gridAxis,
-                                                         beta,
-                                                         numStepsSampleCoord_ * mdTimeStep)),
+    initialHistogramSize(
+            getInitialHistogramSizeEstimate(awhBiasParams, gridAxis, beta, numStepsSampleCoord_ * mdTimeStep)),
     convolveForce(awhParams.ePotential == eawhpotentialCONVOLVED),
     biasIndex(biasIndex),
     disableUpdateSkips_(disableUpdateSkips == DisableUpdateSkips::yes)
index c7b0cedadd6e0d7c277ced3e7f044566be455095..a0f8aae73531ea71382916a301088e4511016785 100644 (file)
@@ -94,8 +94,9 @@ static AwhTestParameters getAwhTestParameters(int eawhgrowth, int eawhpotential)
 
     AwhDimParams& awhDimParams = params.awhDimParams;
 
-    awhDimParams.period         = 0;
-    awhDimParams.diffusion      = 0.1;
+    awhDimParams.period = 0;
+    // Correction for removal of GaussianGeometryFactor/2 in histogram size
+    awhDimParams.diffusion      = 0.1 / (0.144129616073222 * 2);
     awhDimParams.origin         = 0.5;
     awhDimParams.end            = 1.5;
     awhDimParams.coordValueInit = awhDimParams.origin;
index e716966043e99895de242eaa04ef479e3ca387ef..a68095e0100cbfe68f3b6b5e22f9ff74eee0095c 100644 (file)
@@ -96,8 +96,9 @@ static AwhFepLambdaStateTestParameters getAwhFepLambdaTestParameters(int eawhgro
 
     AwhDimParams& awhDimParams = params.awhDimParams;
 
-    awhDimParams.period         = 0;
-    awhDimParams.diffusion      = 1e-4;
+    awhDimParams.period = 0;
+    // Correction for removal of GaussianGeometryFactor/2 in histogram size
+    awhDimParams.diffusion      = 1e-4 / (0.12927243028700 * 2);
     awhDimParams.origin         = 0;
     awhDimParams.end            = numLambdaStates - 1;
     awhDimParams.coordValueInit = awhDimParams.origin;