Fix random typos
[alexxy/gromacs.git] / src / gromacs / applied_forces / awh / biasstate.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2015,2016,2017,2018,2019, The GROMACS development team.
5  * Copyright (c) 2020,2021, by the GROMACS development team, led by
6  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7  * and including many others, as listed in the AUTHORS file in the
8  * top-level source directory and at http://www.gromacs.org.
9  *
10  * GROMACS is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * as published by the Free Software Foundation; either version 2.1
13  * of the License, or (at your option) any later version.
14  *
15  * GROMACS is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with GROMACS; if not, see
22  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
24  *
25  * If you want to redistribute modifications to GROMACS, please
26  * consider that scientific software is very special. Version
27  * control is crucial - bugs must be traceable. We will be happy to
28  * consider code for inclusion in the official distribution, but
29  * derived work must not be called official GROMACS. Details are found
30  * in the README & COPYING files - if they are missing, get the
31  * official version at http://www.gromacs.org.
32  *
33  * To help us fund GROMACS development, we humbly ask that you cite
34  * the research papers on the package. Check out http://www.gromacs.org.
35  */
36
37 /*! \internal \file
38  * \brief
39  * Implements the BiasState class.
40  *
41  * \author Viveca Lindahl
42  * \author Berk Hess <hess@kth.se>
43  * \ingroup module_awh
44  */
45
46 #include "gmxpre.h"
47
48 #include "biasstate.h"
49
50 #include <cassert>
51 #include <cmath>
52 #include <cstdio>
53 #include <cstdlib>
54 #include <cstring>
55
56 #include <algorithm>
57 #include <optional>
58
59 #include "gromacs/fileio/gmxfio.h"
60 #include "gromacs/fileio/xvgr.h"
61 #include "gromacs/gmxlib/network.h"
62 #include "gromacs/math/units.h"
63 #include "gromacs/math/utilities.h"
64 #include "gromacs/mdrunutility/multisim.h"
65 #include "gromacs/mdtypes/awh_history.h"
66 #include "gromacs/mdtypes/awh_params.h"
67 #include "gromacs/mdtypes/commrec.h"
68 #include "gromacs/simd/simd.h"
69 #include "gromacs/simd/simd_math.h"
70 #include "gromacs/utility/arrayref.h"
71 #include "gromacs/utility/exceptions.h"
72 #include "gromacs/utility/gmxassert.h"
73 #include "gromacs/utility/smalloc.h"
74 #include "gromacs/utility/stringutil.h"
75
76 #include "biasgrid.h"
77 #include "biassharing.h"
78 #include "pointstate.h"
79
80 namespace gmx
81 {
82
83 void BiasState::getPmf(gmx::ArrayRef<float> pmf) const
84 {
85     GMX_ASSERT(pmf.size() == points_.size(), "pmf should have the size of the bias grid");
86
87     /* The PMF is just the negative of the log of the sampled PMF histogram.
88      * Points with zero target weight are ignored, they will mostly contain noise.
89      */
90     for (size_t i = 0; i < points_.size(); i++)
91     {
92         pmf[i] = points_[i].inTargetRegion() ? -points_[i].logPmfSum() : GMX_FLOAT_MAX;
93     }
94 }
95
96 namespace
97 {
98
99 /*! \brief
100  * Sum PMF over multiple simulations, when requested.
101  *
102  * \param[in,out] pointState         The state of the points in the bias.
103  * \param[in]     numSharedUpdate    The number of biases sharing the histogram.
104  * \param[in]     biasSharing        Object for sharing bias data over multiple simulations
105  * \param[in]     biasIndex          Index of this bias in the total list of biases in this simulation
106  */
107 void sumPmf(gmx::ArrayRef<PointState> pointState, int numSharedUpdate, const BiasSharing* biasSharing, const int biasIndex)
108 {
109     if (numSharedUpdate == 1)
110     {
111         return;
112     }
113     GMX_ASSERT(biasSharing != nullptr
114                        && numSharedUpdate % biasSharing->numSharingSimulations(biasIndex) == 0,
115                "numSharedUpdate should be a multiple of multiSimComm->numSimulations_");
116     GMX_ASSERT(numSharedUpdate == biasSharing->numSharingSimulations(biasIndex),
117                "Sharing within a simulation is not implemented (yet)");
118
119     std::vector<double> buffer(pointState.size());
120
121     /* Need to temporarily exponentiate the log weights to sum over simulations */
122     for (size_t i = 0; i < buffer.size(); i++)
123     {
124         buffer[i] = pointState[i].inTargetRegion() ? std::exp(pointState[i].logPmfSum()) : 0;
125     }
126
127     biasSharing->sumOverSharingSimulations(gmx::ArrayRef<double>(buffer), biasIndex);
128
129     /* Take log again to get (non-normalized) PMF */
130     double normFac = 1.0 / numSharedUpdate;
131     for (gmx::index i = 0; i < pointState.ssize(); i++)
132     {
133         if (pointState[i].inTargetRegion())
134         {
135             pointState[i].setLogPmfSum(std::log(buffer[i] * normFac));
136         }
137     }
138 }
139
140 /*! \brief
141  * Find the minimum free energy value.
142  *
143  * \param[in] pointState  The state of the points.
144  * \returns the minimum free energy value.
145  */
146 double freeEnergyMinimumValue(gmx::ArrayRef<const PointState> pointState)
147 {
148     double fMin = GMX_FLOAT_MAX;
149
150     for (auto const& ps : pointState)
151     {
152         if (ps.inTargetRegion() && ps.freeEnergy() < fMin)
153         {
154             fMin = ps.freeEnergy();
155         }
156     }
157
158     return fMin;
159 }
160
161 /*! \brief
162  * Find and return the log of the probability weight of a point given a coordinate value.
163  *
164  * The unnormalized weight is given by
165  * w(point|value) = exp(bias(point) - U(value,point)),
166  * where U is a harmonic umbrella potential.
167  *
168  * \param[in] dimParams              The bias dimensions parameters
169  * \param[in] points                 The point state.
170  * \param[in] grid                   The grid.
171  * \param[in] pointIndex             Point to evaluate probability weight for.
172  * \param[in] pointBias              Bias for the point (as a log weight).
173  * \param[in] value                  Coordinate value.
174  * \param[in] neighborLambdaEnergies The energy of the system in neighboring lambdas states. Can be
175  * empty when there are no free energy lambda state dimensions.
176  * \param[in] gridpointIndex         The index of the current grid point.
177  * \returns the log of the biased probability weight.
178  */
179 double biasedLogWeightFromPoint(ArrayRef<const DimParams>  dimParams,
180                                 ArrayRef<const PointState> points,
181                                 const BiasGrid&            grid,
182                                 int                        pointIndex,
183                                 double                     pointBias,
184                                 const awh_dvec             value,
185                                 ArrayRef<const double>     neighborLambdaEnergies,
186                                 int                        gridpointIndex)
187 {
188     double logWeight = detail::c_largeNegativeExponent;
189
190     /* Only points in the target region have non-zero weight */
191     if (points[pointIndex].inTargetRegion())
192     {
193         logWeight = pointBias;
194
195         /* Add potential for all parameter dimensions */
196         for (size_t d = 0; d < dimParams.size(); d++)
197         {
198             if (dimParams[d].isFepLambdaDimension())
199             {
200                 /* If this is not a sampling step or if this function is called from
201                  * calcConvolvedBias(), when writing energy subblocks, neighborLambdaEnergies will
202                  * be empty. No convolution is required along the lambda dimension. */
203                 if (!neighborLambdaEnergies.empty())
204                 {
205                     const int pointLambdaIndex     = grid.point(pointIndex).coordValue[d];
206                     const int gridpointLambdaIndex = grid.point(gridpointIndex).coordValue[d];
207                     logWeight -= dimParams[d].fepDimParams().beta
208                                  * (neighborLambdaEnergies[pointLambdaIndex]
209                                     - neighborLambdaEnergies[gridpointLambdaIndex]);
210                 }
211             }
212             else
213             {
214                 double dev = getDeviationFromPointAlongGridAxis(grid, d, pointIndex, value[d]);
215                 logWeight -= 0.5 * dimParams[d].pullDimParams().betak * dev * dev;
216             }
217         }
218     }
219     return logWeight;
220 }
221
222 /*! \brief
223  * Calculates the marginal distribution (marginal probability) for each value along
224  * a free energy lambda axis.
225  * The marginal distribution of one coordinate dimension value is the sum of the probability
226  * distribution of all values (herein all neighbor values) with the same value in the dimension
227  * of interest.
228  * \param[in] grid               The bias grid.
229  * \param[in] neighbors          The points to use for the calculation of the marginal distribution.
230  * \param[in] probWeightNeighbor Probability weights of the neighbors.
231  * \returns The calculated marginal distribution in a 1D array with
232  * as many elements as there are points along the axis of interest.
233  */
234 std::vector<double> calculateFELambdaMarginalDistribution(const BiasGrid&        grid,
235                                                           ArrayRef<const int>    neighbors,
236                                                           ArrayRef<const double> probWeightNeighbor)
237 {
238     const std::optional<int> lambdaAxisIndex = grid.lambdaAxisIndex();
239     GMX_RELEASE_ASSERT(lambdaAxisIndex,
240                        "There must be a free energy lambda axis in order to calculate the free "
241                        "energy lambda marginal distribution.");
242     const int           numFepLambdaStates = grid.numFepLambdaStates();
243     std::vector<double> lambdaMarginalDistribution(numFepLambdaStates, 0);
244
245     for (size_t i = 0; i < neighbors.size(); i++)
246     {
247         const int neighbor    = neighbors[i];
248         const int lambdaState = grid.point(neighbor).coordValue[lambdaAxisIndex.value()];
249         lambdaMarginalDistribution[lambdaState] += probWeightNeighbor[i];
250     }
251     return lambdaMarginalDistribution;
252 }
253
254 } // namespace
255
256 void BiasState::calcConvolvedPmf(ArrayRef<const DimParams> dimParams,
257                                  const BiasGrid&           grid,
258                                  std::vector<float>*       convolvedPmf) const
259 {
260     size_t numPoints = grid.numPoints();
261
262     convolvedPmf->resize(numPoints);
263
264     /* Get the PMF to convolve. */
265     std::vector<float> pmf(numPoints);
266     getPmf(pmf);
267
268     for (size_t m = 0; m < numPoints; m++)
269     {
270         double           freeEnergyWeights = 0;
271         const GridPoint& point             = grid.point(m);
272         for (const auto& neighbor : point.neighbor)
273         {
274             /* Do not convolve the bias along a lambda axis - only use the pmf from the current point */
275             if (!pointsHaveDifferentLambda(grid, m, neighbor))
276             {
277                 /* The negative PMF is a positive bias. */
278                 double biasNeighbor = -pmf[neighbor];
279
280                 /* Add the convolved PMF weights for the neighbors of this point.
281                 Note that this function only adds point within the target > 0 region.
282                 Sum weights, take the logarithm last to get the free energy. */
283                 double logWeight = biasedLogWeightFromPoint(
284                         dimParams, points_, grid, neighbor, biasNeighbor, point.coordValue, {}, m);
285                 freeEnergyWeights += std::exp(logWeight);
286             }
287         }
288
289         GMX_RELEASE_ASSERT(freeEnergyWeights > 0,
290                            "Attempting to do log(<= 0) in AWH convolved PMF calculation.");
291         (*convolvedPmf)[m] = -std::log(static_cast<float>(freeEnergyWeights));
292     }
293 }
294
295 namespace
296 {
297
298 /*! \brief
299  * Updates the target distribution for all points.
300  *
301  * The target distribution is always updated for all points
302  * at the same time.
303  *
304  * \param[in,out] pointState  The state of all points.
305  * \param[in]     params      The bias parameters.
306  */
307 void updateTargetDistribution(ArrayRef<PointState> pointState, const BiasParams& params)
308 {
309     double freeEnergyCutoff = 0;
310     if (params.eTarget == AwhTargetType::Cutoff)
311     {
312         freeEnergyCutoff = freeEnergyMinimumValue(pointState) + params.freeEnergyCutoffInKT;
313     }
314
315     double sumTarget = 0;
316     for (PointState& ps : pointState)
317     {
318         sumTarget += ps.updateTargetWeight(params, freeEnergyCutoff);
319     }
320     GMX_RELEASE_ASSERT(sumTarget > 0, "We should have a non-zero distribution");
321
322     /* Normalize to 1 */
323     double invSum = 1.0 / sumTarget;
324     for (PointState& ps : pointState)
325     {
326         ps.scaleTarget(invSum);
327     }
328 }
329
330 /*! \brief
331  * Puts together a string describing a grid point.
332  *
333  * \param[in] grid         The grid.
334  * \param[in] point        BiasGrid point index.
335  * \returns a string for the point.
336  */
337 std::string gridPointValueString(const BiasGrid& grid, int point)
338 {
339     std::string pointString;
340
341     pointString += "(";
342
343     for (int d = 0; d < grid.numDimensions(); d++)
344     {
345         pointString += gmx::formatString("%g", grid.point(point).coordValue[d]);
346         if (d < grid.numDimensions() - 1)
347         {
348             pointString += ",";
349         }
350         else
351         {
352             pointString += ")";
353         }
354     }
355
356     return pointString;
357 }
358
359 } // namespace
360
361 int BiasState::warnForHistogramAnomalies(const BiasGrid& grid, int biasIndex, double t, FILE* fplog, int maxNumWarnings) const
362 {
363     GMX_ASSERT(fplog != nullptr, "Warnings can only be issued if there is log file.");
364     const double maxHistogramRatio = 0.5; /* Tolerance for printing a warning about the histogram ratios */
365
366     /* Sum up the histograms and get their normalization */
367     double sumVisits  = 0;
368     double sumWeights = 0;
369     for (const auto& pointState : points_)
370     {
371         if (pointState.inTargetRegion())
372         {
373             sumVisits += pointState.numVisitsTot();
374             sumWeights += pointState.weightSumTot();
375         }
376     }
377     GMX_RELEASE_ASSERT(sumVisits > 0, "We should have visits");
378     GMX_RELEASE_ASSERT(sumWeights > 0, "We should have weight");
379     double invNormVisits = 1.0 / sumVisits;
380     double invNormWeight = 1.0 / sumWeights;
381
382     /* Check all points for warnings */
383     int    numWarnings = 0;
384     size_t numPoints   = grid.numPoints();
385     for (size_t m = 0; m < numPoints; m++)
386     {
387         /* Skip points close to boundary or non-target region */
388         const GridPoint& gridPoint = grid.point(m);
389         bool             skipPoint = false;
390         for (size_t n = 0; (n < gridPoint.neighbor.size()) && !skipPoint; n++)
391         {
392             int neighbor = gridPoint.neighbor[n];
393             skipPoint    = !points_[neighbor].inTargetRegion();
394             for (int d = 0; (d < grid.numDimensions()) && !skipPoint; d++)
395             {
396                 const GridPoint& neighborPoint = grid.point(neighbor);
397                 skipPoint                      = neighborPoint.index[d] == 0
398                             || neighborPoint.index[d] == grid.axis(d).numPoints() - 1;
399             }
400         }
401
402         /* Warn if the coordinate distribution is less than the target distribution with a certain fraction somewhere */
403         const double relativeWeight = points_[m].weightSumTot() * invNormWeight;
404         const double relativeVisits = points_[m].numVisitsTot() * invNormVisits;
405         if (!skipPoint && relativeVisits < relativeWeight * maxHistogramRatio)
406         {
407             std::string pointValueString = gridPointValueString(grid, m);
408             std::string warningMessage   = gmx::formatString(
409                     "\nawh%d warning: "
410                     "at t = %g ps the obtained coordinate distribution at coordinate value %s "
411                     "is less than a fraction %g of the reference distribution at that point. "
412                     "If you are not certain about your settings you might want to increase your "
413                     "pull force constant or "
414                     "modify your sampling region.\n",
415                     biasIndex + 1,
416                     t,
417                     pointValueString.c_str(),
418                     maxHistogramRatio);
419             gmx::TextLineWrapper wrapper;
420             wrapper.settings().setLineLength(c_linewidth);
421             fprintf(fplog, "%s", wrapper.wrapToString(warningMessage).c_str());
422
423             numWarnings++;
424         }
425         if (numWarnings >= maxNumWarnings)
426         {
427             break;
428         }
429     }
430
431     return numWarnings;
432 }
433
434 double BiasState::calcUmbrellaForceAndPotential(ArrayRef<const DimParams> dimParams,
435                                                 const BiasGrid&           grid,
436                                                 int                       point,
437                                                 ArrayRef<const double>    neighborLambdaDhdl,
438                                                 ArrayRef<double>          force) const
439 {
440     double potential = 0;
441     for (size_t d = 0; d < dimParams.size(); d++)
442     {
443         if (dimParams[d].isFepLambdaDimension())
444         {
445             if (!neighborLambdaDhdl.empty())
446             {
447                 const int coordpointLambdaIndex = grid.point(point).coordValue[d];
448                 force[d]                        = neighborLambdaDhdl[coordpointLambdaIndex];
449                 /* The potential should not be affected by the lambda dimension. */
450             }
451         }
452         else
453         {
454             double deviation =
455                     getDeviationFromPointAlongGridAxis(grid, d, point, coordState_.coordValue()[d]);
456             double k = dimParams[d].pullDimParams().k;
457
458             /* Force from harmonic potential 0.5*k*dev^2 */
459             force[d] = -k * deviation;
460             potential += 0.5 * k * deviation * deviation;
461         }
462     }
463
464     return potential;
465 }
466
467 void BiasState::calcConvolvedForce(ArrayRef<const DimParams> dimParams,
468                                    const BiasGrid&           grid,
469                                    ArrayRef<const double>    probWeightNeighbor,
470                                    ArrayRef<const double>    neighborLambdaDhdl,
471                                    ArrayRef<double>          forceWorkBuffer,
472                                    ArrayRef<double>          force) const
473 {
474     for (size_t d = 0; d < dimParams.size(); d++)
475     {
476         force[d] = 0;
477     }
478
479     /* Only neighboring points have non-negligible contribution. */
480     const std::vector<int>& neighbor          = grid.point(coordState_.gridpointIndex()).neighbor;
481     gmx::ArrayRef<double>   forceFromNeighbor = forceWorkBuffer;
482     for (size_t n = 0; n < neighbor.size(); n++)
483     {
484         double weightNeighbor = probWeightNeighbor[n];
485         int    indexNeighbor  = neighbor[n];
486
487         /* Get the umbrella force from this point. The returned potential is ignored here. */
488         calcUmbrellaForceAndPotential(dimParams, grid, indexNeighbor, neighborLambdaDhdl, forceFromNeighbor);
489
490         /* Add the weighted umbrella force to the convolved force. */
491         for (size_t d = 0; d < dimParams.size(); d++)
492         {
493             force[d] += forceFromNeighbor[d] * weightNeighbor;
494         }
495     }
496 }
497
498 double BiasState::moveUmbrella(ArrayRef<const DimParams> dimParams,
499                                const BiasGrid&           grid,
500                                ArrayRef<const double>    probWeightNeighbor,
501                                ArrayRef<const double>    neighborLambdaDhdl,
502                                ArrayRef<double>          biasForce,
503                                int64_t                   step,
504                                int64_t                   seed,
505                                int                       indexSeed,
506                                bool                      onlySampleUmbrellaGridpoint)
507 {
508     /* Generate and set a new coordinate reference value */
509     coordState_.sampleUmbrellaGridpoint(
510             grid, coordState_.gridpointIndex(), probWeightNeighbor, step, seed, indexSeed);
511
512     if (onlySampleUmbrellaGridpoint)
513     {
514         return 0;
515     }
516
517     std::vector<double> newForce(dimParams.size());
518     double              newPotential = calcUmbrellaForceAndPotential(
519             dimParams, grid, coordState_.umbrellaGridpoint(), neighborLambdaDhdl, newForce);
520
521     /*  A modification of the reference value at time t will lead to a different
522         force over t-dt/2 to t and over t to t+dt/2. For high switching rates
523         this means the force and velocity will change signs roughly as often.
524         To avoid any issues we take the average of the previous and new force
525         at steps when the reference value has been moved. E.g. if the ref. value
526         is set every step to (coord dvalue +/- delta) would give zero force.
527      */
528     for (gmx::index d = 0; d < biasForce.ssize(); d++)
529     {
530         /* Average of the current and new force */
531         biasForce[d] = 0.5 * (biasForce[d] + newForce[d]);
532     }
533
534     return newPotential;
535 }
536
537 namespace
538 {
539
540 /*! \brief
541  * Sets the histogram rescaling factors needed to control the histogram size.
542  *
543  * For sake of robustness, the reference weight histogram can grow at a rate
544  * different from the actual sampling rate. Typically this happens for a limited
545  * initial time, alternatively growth is scaled down by a constant factor for all
546  * times. Since the size of the reference histogram sets the size of the free
547  * energy update this should be reflected also in the PMF. Thus the PMF histogram
548  * needs to be rescaled too.
549  *
550  * This function should only be called by the bias update function or wrapped by a function that
551  * knows what scale factors should be applied when, e.g,
552  * getSkippedUpdateHistogramScaleFactors().
553  *
554  * \param[in]  params             The bias parameters.
555  * \param[in]  newHistogramSize   New reference weight histogram size.
556  * \param[in]  oldHistogramSize   Previous reference weight histogram size (before adding new samples).
557  * \param[out] weightHistScaling  Scaling factor for the reference weight histogram.
558  * \param[out] logPmfSumScaling   Log of the scaling factor for the PMF histogram.
559  */
560 void setHistogramUpdateScaleFactors(const BiasParams& params,
561                                     double            newHistogramSize,
562                                     double            oldHistogramSize,
563                                     double*           weightHistScaling,
564                                     double*           logPmfSumScaling)
565 {
566
567     /* The two scaling factors below are slightly different (ignoring the log factor) because the
568        reference and the PMF histogram apply weight scaling differently. The weight histogram
569        applies is  locally, i.e. each sample is scaled down meaning all samples get equal weight.
570        It is done this way because that is what target type local Boltzmann (for which
571        target = weight histogram) needs. In contrast, the PMF histogram is rescaled globally
572        by repeatedly scaling down the whole histogram. The reasons for doing it this way are:
573        1) empirically this is necessary for converging the PMF; 2) since the extraction of
574        the PMF is theoretically only valid for a constant bias, new samples should get more
575        weight than old ones for which the bias is fluctuating more. */
576     *weightHistScaling =
577             newHistogramSize / (oldHistogramSize + params.updateWeight * params.localWeightScaling);
578     *logPmfSumScaling = std::log(newHistogramSize / (oldHistogramSize + params.updateWeight));
579 }
580
581 } // namespace
582
583 void BiasState::getSkippedUpdateHistogramScaleFactors(const BiasParams& params,
584                                                       double*           weightHistScaling,
585                                                       double*           logPmfSumScaling) const
586 {
587     GMX_ASSERT(params.skipUpdates(),
588                "Calling function for skipped updates when skipping updates is not allowed");
589
590     if (inInitialStage())
591     {
592         /* In between global updates the reference histogram size is kept constant so we trivially
593            know what the histogram size was at the time of the skipped update. */
594         double histogramSize = histogramSize_.histogramSize();
595         setHistogramUpdateScaleFactors(
596                 params, histogramSize, histogramSize, weightHistScaling, logPmfSumScaling);
597     }
598     else
599     {
600         /* In the final stage, the reference histogram grows at the sampling rate which gives trivial scale factors. */
601         *weightHistScaling = 1;
602         *logPmfSumScaling  = 0;
603     }
604 }
605
606 void BiasState::doSkippedUpdatesForAllPoints(const BiasParams& params)
607 {
608     double weightHistScaling;
609     double logPmfsumScaling;
610
611     getSkippedUpdateHistogramScaleFactors(params, &weightHistScaling, &logPmfsumScaling);
612
613     for (auto& pointState : points_)
614     {
615         bool didUpdate = pointState.performPreviouslySkippedUpdates(
616                 params, histogramSize_.numUpdates(), weightHistScaling, logPmfsumScaling);
617
618         /* Update the bias for this point only if there were skipped updates in the past to avoid calculating the log unneccessarily */
619         if (didUpdate)
620         {
621             pointState.updateBias();
622         }
623     }
624 }
625
626 void BiasState::doSkippedUpdatesInNeighborhood(const BiasParams& params, const BiasGrid& grid)
627 {
628     double weightHistScaling;
629     double logPmfsumScaling;
630
631     getSkippedUpdateHistogramScaleFactors(params, &weightHistScaling, &logPmfsumScaling);
632
633     /* For each neighbor point of the center point, refresh its state by adding the results of all past, skipped updates. */
634     const std::vector<int>& neighbors = grid.point(coordState_.gridpointIndex()).neighbor;
635     for (const auto& neighbor : neighbors)
636     {
637         bool didUpdate = points_[neighbor].performPreviouslySkippedUpdates(
638                 params, histogramSize_.numUpdates(), weightHistScaling, logPmfsumScaling);
639
640         if (didUpdate)
641         {
642             points_[neighbor].updateBias();
643         }
644     }
645 }
646
647 namespace
648 {
649
650 /*! \brief
651  * Merge update lists from multiple sharing simulations.
652  *
653  * \param[in,out] updateList    Update list for this simulation (assumed >= npoints long).
654  * \param[in]     numPoints     Total number of points.
655  * \param[in]     biasSharing   Object for sharing bias data over multiple simulations
656  * \param[in]     biasIndex     Index of this bias in the total list of biases in this simulation
657  */
658 void mergeSharedUpdateLists(std::vector<int>*  updateList,
659                             int                numPoints,
660                             const BiasSharing& biasSharing,
661                             const int          biasIndex)
662 {
663     std::vector<int> numUpdatesOfPoint;
664
665     /* Flag the update points of this sim.
666        TODO: we can probably avoid allocating this array and just use the input array. */
667     numUpdatesOfPoint.resize(numPoints, 0);
668     for (auto& pointIndex : *updateList)
669     {
670         numUpdatesOfPoint[pointIndex] = 1;
671     }
672
673     /* Sum over the sims to get all the flagged points */
674     biasSharing.sumOverSharingSimulations(arrayRefFromArray(numUpdatesOfPoint.data(), numPoints), biasIndex);
675
676     /* Collect the indices of the flagged points in place. The resulting array will be the merged update list.*/
677     updateList->clear();
678     for (int m = 0; m < numPoints; m++)
679     {
680         if (numUpdatesOfPoint[m] > 0)
681         {
682             updateList->push_back(m);
683         }
684     }
685 }
686
687 /*! \brief
688  * Generate an update list of points sampled since the last update.
689  *
690  * \param[in] grid              The AWH bias.
691  * \param[in] points            The point state.
692  * \param[in] originUpdatelist  The origin of the rectangular region that has been sampled since
693  *                              last update.
694  * \param[in] endUpdatelist     The end of the rectangular that has been sampled since
695  *                              last update.
696  * \param[in,out] updateList    Local update list to set (assumed >= npoints long).
697  */
698 void makeLocalUpdateList(const BiasGrid&            grid,
699                          ArrayRef<const PointState> points,
700                          const awh_ivec             originUpdatelist,
701                          const awh_ivec             endUpdatelist,
702                          std::vector<int>*          updateList)
703 {
704     awh_ivec origin;
705     awh_ivec numPoints;
706
707     /* Define the update search grid */
708     for (int d = 0; d < grid.numDimensions(); d++)
709     {
710         origin[d]    = originUpdatelist[d];
711         numPoints[d] = endUpdatelist[d] - originUpdatelist[d] + 1;
712
713         /* Because the end_updatelist is unwrapped it can be > (npoints - 1) so that numPoints can be > npoints in grid.
714            This helps for calculating the distance/number of points but should be removed and fixed when the way of
715            updating origin/end updatelist is changed (see sampleProbabilityWeights). */
716         numPoints[d] = std::min(grid.axis(d).numPoints(), numPoints[d]);
717     }
718
719     /* Make the update list */
720     updateList->clear();
721     int  pointIndex  = -1;
722     bool pointExists = true;
723     while (pointExists)
724     {
725         pointExists = advancePointInSubgrid(grid, origin, numPoints, &pointIndex);
726
727         if (pointExists && points[pointIndex].inTargetRegion())
728         {
729             updateList->push_back(pointIndex);
730         }
731     }
732 }
733
734 } // namespace
735
736 void BiasState::resetLocalUpdateRange(const BiasGrid& grid)
737 {
738     const int gridpointIndex = coordState_.gridpointIndex();
739     for (int d = 0; d < grid.numDimensions(); d++)
740     {
741         /* This gives the  minimum range consisting only of the current closest point. */
742         originUpdatelist_[d] = grid.point(gridpointIndex).index[d];
743         endUpdatelist_[d]    = grid.point(gridpointIndex).index[d];
744     }
745 }
746
747 namespace
748 {
749
750 /*! \brief
751  * Add partial histograms (accumulating between updates) to accumulating histograms.
752  *
753  * \param[in,out] pointState         The state of the points in the bias.
754  * \param[in,out] weightSumCovering  The weights for checking covering.
755  * \param[in]     numSharedUpdate    The number of biases sharing the histrogram.
756  * \param[in]     biasSharing        Object for sharing bias data over multiple simulations
757  * \param[in]     biasIndex          Index of this bias in the total list of biases in this
758  * simulation \param[in]     localUpdateList    List of points with data.
759  */
760 void sumHistograms(gmx::ArrayRef<PointState> pointState,
761                    gmx::ArrayRef<double>     weightSumCovering,
762                    int                       numSharedUpdate,
763                    const BiasSharing*        biasSharing,
764                    const int                 biasIndex,
765                    const std::vector<int>&   localUpdateList)
766 {
767     /* The covering checking histograms are added before summing over simulations, so that the
768        weights from different simulations are kept distinguishable. */
769     for (int globalIndex : localUpdateList)
770     {
771         weightSumCovering[globalIndex] += pointState[globalIndex].weightSumIteration();
772     }
773
774     /* Sum histograms over multiple simulations if needed. */
775     if (numSharedUpdate > 1)
776     {
777         GMX_ASSERT(numSharedUpdate == biasSharing->numSharingSimulations(biasIndex),
778                    "Sharing within a simulation is not implemented (yet)");
779
780         /* Collect the weights and counts in linear arrays to be able to use gmx_sumd_sim. */
781         std::vector<double> weightSum;
782         std::vector<double> coordVisits;
783
784         weightSum.resize(localUpdateList.size());
785         coordVisits.resize(localUpdateList.size());
786
787         for (size_t localIndex = 0; localIndex < localUpdateList.size(); localIndex++)
788         {
789             const PointState& ps = pointState[localUpdateList[localIndex]];
790
791             weightSum[localIndex]   = ps.weightSumIteration();
792             coordVisits[localIndex] = ps.numVisitsIteration();
793         }
794
795         biasSharing->sumOverSharingSimulations(gmx::ArrayRef<double>(weightSum), biasIndex);
796         biasSharing->sumOverSharingSimulations(gmx::ArrayRef<double>(coordVisits), biasIndex);
797
798         /* Transfer back the result */
799         for (size_t localIndex = 0; localIndex < localUpdateList.size(); localIndex++)
800         {
801             PointState& ps = pointState[localUpdateList[localIndex]];
802
803             ps.setPartialWeightAndCount(weightSum[localIndex], coordVisits[localIndex]);
804         }
805     }
806
807     /* Now add the partial counts and weights to the accumulating histograms.
808        Note: we still need to use the weights for the update so we wait
809        with resetting them until the end of the update. */
810     for (int globalIndex : localUpdateList)
811     {
812         pointState[globalIndex].addPartialWeightAndCount();
813     }
814 }
815
816 /*! \brief
817  * Label points along an axis as covered or not.
818  *
819  * A point is covered if it is surrounded by visited points up to a radius = coverRadius.
820  *
821  * \param[in]     visited        Visited? For each point.
822  * \param[in]     checkCovering  Check for covering? For each point.
823  * \param[in]     numPoints      The number of grid points along this dimension.
824  * \param[in]     period         Period in number of points.
825  * \param[in]     coverRadius    Cover radius, in points, needed for defining a point as covered.
826  * \param[in,out] covered        In this array elements are 1 for covered points and 0 for
827  * non-covered points, this routine assumes that \p covered has at least size \p numPoints.
828  */
829 void labelCoveredPoints(const std::vector<bool>& visited,
830                         const std::vector<bool>& checkCovering,
831                         int                      numPoints,
832                         int                      period,
833                         int                      coverRadius,
834                         gmx::ArrayRef<int>       covered)
835 {
836     GMX_ASSERT(covered.ssize() >= numPoints, "covered should be at least as large as the grid");
837
838     bool haveFirstNotVisited = false;
839     int  firstNotVisited     = -1;
840     int  notVisitedLow       = -1;
841     int  notVisitedHigh      = -1;
842
843     for (int n = 0; n < numPoints; n++)
844     {
845         if (checkCovering[n] && !visited[n])
846         {
847             if (!haveFirstNotVisited)
848             {
849                 notVisitedLow       = n;
850                 firstNotVisited     = n;
851                 haveFirstNotVisited = true;
852             }
853             else
854             {
855                 notVisitedHigh = n;
856
857                 /* Have now an interval I = [notVisitedLow,notVisitedHigh] of visited points bounded
858                    by unvisited points. The unvisted end points affect the coveredness of the
859                    visited with a reach equal to the cover radius. */
860                 int notCoveredLow  = notVisitedLow + coverRadius;
861                 int notCoveredHigh = notVisitedHigh - coverRadius;
862                 for (int i = notVisitedLow; i <= notVisitedHigh; i++)
863                 {
864                     covered[i] = static_cast<int>((i > notCoveredLow) && (i < notCoveredHigh));
865                 }
866
867                 /* Find a new interval to set covering for. Make the notVisitedHigh of this interval
868                    the notVisitedLow of the next. */
869                 notVisitedLow = notVisitedHigh;
870             }
871         }
872     }
873
874     /* Have labelled all the internal points. Now take care of the boundary regions. */
875     if (!haveFirstNotVisited)
876     {
877         /* No non-visited points <=> all points visited => all points covered. */
878
879         for (int n = 0; n < numPoints; n++)
880         {
881             covered[n] = 1;
882         }
883     }
884     else
885     {
886         int lastNotVisited = notVisitedLow;
887
888         /* For periodic boundaries, non-visited points can influence points
889            on the other side of the boundary so we need to wrap around. */
890
891         /* Lower end. For periodic boundaries the last upper end not visited point becomes the low-end not visited point.
892            For non-periodic boundaries there is no lower end point so a dummy value is used. */
893         int notVisitedHigh = firstNotVisited;
894         int notVisitedLow  = period > 0 ? (lastNotVisited - period) : -(coverRadius + 1);
895
896         int notCoveredLow  = notVisitedLow + coverRadius;
897         int notCoveredHigh = notVisitedHigh - coverRadius;
898
899         for (int i = 0; i <= notVisitedHigh; i++)
900         {
901             /* For non-periodic boundaries notCoveredLow = -1 will impose no restriction. */
902             covered[i] = static_cast<int>((i > notCoveredLow) && (i < notCoveredHigh));
903         }
904
905         /* Upper end. Same as for lower end but in the other direction. */
906         notVisitedHigh = period > 0 ? (firstNotVisited + period) : (numPoints + coverRadius);
907         notVisitedLow  = lastNotVisited;
908
909         notCoveredLow  = notVisitedLow + coverRadius;
910         notCoveredHigh = notVisitedHigh - coverRadius;
911
912         for (int i = notVisitedLow; i <= numPoints - 1; i++)
913         {
914             /* For non-periodic boundaries notCoveredHigh = numPoints will impose no restriction. */
915             covered[i] = static_cast<int>((i > notCoveredLow) && (i < notCoveredHigh));
916         }
917     }
918 }
919
920 } // namespace
921
922 bool BiasState::isSamplingRegionCovered(const BiasParams&         params,
923                                         ArrayRef<const DimParams> dimParams,
924                                         const BiasGrid&           grid) const
925 {
926     /* Allocate and initialize arrays: one for checking visits along each dimension,
927        one for keeping track of which points to check and one for the covered points.
928        Possibly these could be kept as AWH variables to avoid these allocations. */
929     struct CheckDim
930     {
931         std::vector<bool> visited;
932         std::vector<bool> checkCovering;
933         // We use int for the covering array since we might use gmx_sumi_sim.
934         std::vector<int> covered;
935     };
936
937     std::vector<CheckDim> checkDim;
938     checkDim.resize(grid.numDimensions());
939
940     for (int d = 0; d < grid.numDimensions(); d++)
941     {
942         const size_t numPoints = grid.axis(d).numPoints();
943         checkDim[d].visited.resize(numPoints, false);
944         checkDim[d].checkCovering.resize(numPoints, false);
945         checkDim[d].covered.resize(numPoints, 0);
946     }
947
948     /* Set visited points along each dimension and which points should be checked for covering.
949        Specifically, points above the free energy cutoff (if there is one) or points outside
950        of the target region are ignored. */
951
952     /* Set the free energy cutoff */
953     double maxFreeEnergy = GMX_FLOAT_MAX;
954
955     if (params.eTarget == AwhTargetType::Cutoff)
956     {
957         maxFreeEnergy = freeEnergyMinimumValue(points_) + params.freeEnergyCutoffInKT;
958     }
959
960     /* Set the threshold weight for a point to be considered visited. */
961     double weightThreshold = 1;
962     for (int d = 0; d < grid.numDimensions(); d++)
963     {
964         if (grid.axis(d).isFepLambdaAxis())
965         {
966             /* Do not modify the weight threshold based on a FEP lambda axis. The spread
967              * of the sampling weights is not depending on a Gaussian distribution (like
968              * below). */
969             weightThreshold *= 1.0;
970         }
971         else
972         {
973             /* The spacing is proportional to 1/sqrt(betak). The weight threshold will be
974              * approximately (given that the spacing can be modified if the dimension is periodic)
975              * proportional to sqrt(1/(2*pi)). */
976             weightThreshold *= grid.axis(d).spacing()
977                                * std::sqrt(dimParams[d].pullDimParams().betak * 0.5 * M_1_PI);
978         }
979     }
980
981     /* Project the sampling weights onto each dimension */
982     for (size_t m = 0; m < grid.numPoints(); m++)
983     {
984         const PointState& pointState = points_[m];
985
986         for (int d = 0; d < grid.numDimensions(); d++)
987         {
988             int n = grid.point(m).index[d];
989
990             /* Is visited if it was already visited or if there is enough weight at the current point */
991             checkDim[d].visited[n] = checkDim[d].visited[n] || (weightSumCovering_[m] > weightThreshold);
992
993             /* Check for covering if there is at least point in this slice that is in the target region and within the cutoff */
994             checkDim[d].checkCovering[n] =
995                     checkDim[d].checkCovering[n]
996                     || (pointState.inTargetRegion() && pointState.freeEnergy() < maxFreeEnergy);
997         }
998     }
999
1000     /* Label each point along each dimension as covered or not. */
1001     for (int d = 0; d < grid.numDimensions(); d++)
1002     {
1003         labelCoveredPoints(checkDim[d].visited,
1004                            checkDim[d].checkCovering,
1005                            grid.axis(d).numPoints(),
1006                            grid.axis(d).numPointsInPeriod(),
1007                            params.coverRadius()[d],
1008                            checkDim[d].covered);
1009     }
1010
1011     /* Now check for global covering. Each dimension needs to be covered separately.
1012        A dimension is covered if each point is covered.  Multiple simulations collectively
1013        cover the points, i.e. a point is covered if any of the simulations covered it.
1014        However, visited points are not shared, i.e. if a point is covered or not is
1015        determined by the visits of a single simulation. In general the covering criterion is
1016        all points covered => all points are surrounded by visited points up to a radius = coverRadius.
1017        For 1 simulation, all points covered <=> all points visited. For multiple simulations
1018        however, all points visited collectively !=> all points covered, except for coverRadius = 0.
1019        In the limit of large coverRadius, all points covered => all points visited by at least one
1020        simulation (since no point will be covered until all points have been visited by a
1021        single simulation). Basically coverRadius sets how much "connectedness" (or mixing) a point
1022        needs with surrounding points before sharing covering information with other simulations. */
1023
1024     /* Communicate the covered points between sharing simulations if needed. */
1025     if (params.numSharedUpdate > 1)
1026     {
1027         /* For multiple dimensions this may not be the best way to do it. */
1028         for (int d = 0; d < grid.numDimensions(); d++)
1029         {
1030             biasSharing_->sumOverSharingSimulations(
1031                     gmx::arrayRefFromArray(checkDim[d].covered.data(), grid.axis(d).numPoints()),
1032                     params.biasIndex);
1033         }
1034     }
1035
1036     /* Now check if for each dimension all points are covered. Break if not true. */
1037     bool allPointsCovered = true;
1038     for (int d = 0; d < grid.numDimensions() && allPointsCovered; d++)
1039     {
1040         for (int n = 0; n < grid.axis(d).numPoints() && allPointsCovered; n++)
1041         {
1042             allPointsCovered = (checkDim[d].covered[n] != 0);
1043         }
1044     }
1045
1046     return allPointsCovered;
1047 }
1048
1049 /*! \brief
1050  * Normalizes the free energy and PMF sum.
1051  *
1052  * \param[in] pointState  The state of the points.
1053  */
1054 static void normalizeFreeEnergyAndPmfSum(std::vector<PointState>* pointState)
1055 {
1056     double minF = freeEnergyMinimumValue(*pointState);
1057
1058     for (PointState& ps : *pointState)
1059     {
1060         ps.normalizeFreeEnergyAndPmfSum(minF);
1061     }
1062 }
1063
1064 void BiasState::updateFreeEnergyAndAddSamplesToHistogram(ArrayRef<const DimParams> dimParams,
1065                                                          const BiasGrid&           grid,
1066                                                          const BiasParams&         params,
1067                                                          double                    t,
1068                                                          int64_t                   step,
1069                                                          FILE*                     fplog,
1070                                                          std::vector<int>*         updateList)
1071 {
1072     /* Note hat updateList is only used in this scope and is always
1073      * re-initialized. We do not use a local vector, because that would
1074      * cause reallocation every time this funtion is called and the vector
1075      * can be the size of the whole grid.
1076      */
1077
1078     /* Make a list of all local points, i.e. those that could have been touched since
1079        the last update. These are the points needed for summing histograms below
1080        (non-local points only add zeros). For local updates, this will also be the
1081        final update list. */
1082     makeLocalUpdateList(grid, points_, originUpdatelist_, endUpdatelist_, updateList);
1083     if (params.numSharedUpdate > 1)
1084     {
1085         mergeSharedUpdateLists(updateList, points_.size(), *biasSharing_, params.biasIndex);
1086     }
1087
1088     /* Reset the range for the next update */
1089     resetLocalUpdateRange(grid);
1090
1091     /* Add samples to histograms for all local points and sync simulations if needed */
1092     sumHistograms(points_, weightSumCovering_, params.numSharedUpdate, biasSharing_, params.biasIndex, *updateList);
1093
1094     sumPmf(points_, params.numSharedUpdate, biasSharing_, params.biasIndex);
1095
1096     /* Renormalize the free energy if values are too large. */
1097     bool needToNormalizeFreeEnergy = false;
1098     for (int& globalIndex : *updateList)
1099     {
1100         /* We want to keep the absolute value of the free energies to be less
1101            c_largePositiveExponent to be able to safely pass these values to exp(). The check below
1102            ensures this as long as the free energy values grow less than 0.5*c_largePositiveExponent
1103            in a return time to this neighborhood. For reasonable update sizes it's unlikely that
1104            this requirement would be broken. */
1105         if (std::abs(points_[globalIndex].freeEnergy()) > 0.5 * detail::c_largePositiveExponent)
1106         {
1107             needToNormalizeFreeEnergy = true;
1108             break;
1109         }
1110     }
1111
1112     /* Update target distribution? */
1113     bool needToUpdateTargetDistribution =
1114             (params.eTarget != AwhTargetType::Constant && params.isUpdateTargetStep(step));
1115
1116     /* In the initial stage, the histogram grows dynamically as a function of the number of coverings. */
1117     bool detectedCovering = false;
1118     if (inInitialStage())
1119     {
1120         detectedCovering =
1121                 (params.isCheckCoveringStep(step) && isSamplingRegionCovered(params, dimParams, grid));
1122     }
1123
1124     /* The weighthistogram size after this update. */
1125     double newHistogramSize = histogramSize_.newHistogramSize(
1126             params, t, detectedCovering, points_, weightSumCovering_, fplog);
1127
1128     /* Make the update list. Usually we try to only update local points,
1129      * but if the update has non-trivial or non-deterministic effects
1130      * on non-local points a global update is needed. This is the case when:
1131      * 1) a covering occurred in the initial stage, leading to non-trivial
1132      *    histogram rescaling factors; or
1133      * 2) the target distribution will be updated, since we don't make any
1134      *    assumption on its form; or
1135      * 3) the AWH parameters are such that we never attempt to skip non-local
1136      *    updates; or
1137      * 4) the free energy values have grown so large that a renormalization
1138      *    is needed.
1139      */
1140     if (needToUpdateTargetDistribution || detectedCovering || !params.skipUpdates() || needToNormalizeFreeEnergy)
1141     {
1142         /* Global update, just add all points. */
1143         updateList->clear();
1144         for (size_t m = 0; m < points_.size(); m++)
1145         {
1146             if (points_[m].inTargetRegion())
1147             {
1148                 updateList->push_back(m);
1149             }
1150         }
1151     }
1152
1153     /* Set histogram scale factors. */
1154     double weightHistScalingSkipped = 0;
1155     double logPmfsumScalingSkipped  = 0;
1156     if (params.skipUpdates())
1157     {
1158         getSkippedUpdateHistogramScaleFactors(params, &weightHistScalingSkipped, &logPmfsumScalingSkipped);
1159     }
1160     double weightHistScalingNew;
1161     double logPmfsumScalingNew;
1162     setHistogramUpdateScaleFactors(
1163             params, newHistogramSize, histogramSize_.histogramSize(), &weightHistScalingNew, &logPmfsumScalingNew);
1164
1165     /* Update free energy and reference weight histogram for points in the update list. */
1166     for (int pointIndex : *updateList)
1167     {
1168         PointState* pointStateToUpdate = &points_[pointIndex];
1169
1170         /* Do updates from previous update steps that were skipped because this point was at that time non-local. */
1171         if (params.skipUpdates())
1172         {
1173             pointStateToUpdate->performPreviouslySkippedUpdates(
1174                     params, histogramSize_.numUpdates(), weightHistScalingSkipped, logPmfsumScalingSkipped);
1175         }
1176
1177         /* Now do an update with new sampling data. */
1178         pointStateToUpdate->updateWithNewSampling(
1179                 params, histogramSize_.numUpdates(), weightHistScalingNew, logPmfsumScalingNew);
1180     }
1181
1182     /* Only update the histogram size after we are done with the local point updates */
1183     histogramSize_.setHistogramSize(newHistogramSize, weightHistScalingNew);
1184
1185     if (needToNormalizeFreeEnergy)
1186     {
1187         normalizeFreeEnergyAndPmfSum(&points_);
1188     }
1189
1190     if (needToUpdateTargetDistribution)
1191     {
1192         /* The target distribution is always updated for all points at once. */
1193         updateTargetDistribution(points_, params);
1194     }
1195
1196     /* Update the bias. The bias is updated separately and last since it simply a function of
1197        the free energy and the target distribution and we want to avoid doing extra work. */
1198     for (int pointIndex : *updateList)
1199     {
1200         points_[pointIndex].updateBias();
1201     }
1202
1203     /* Increase the update counter. */
1204     histogramSize_.incrementNumUpdates();
1205 }
1206
1207 double BiasState::updateProbabilityWeightsAndConvolvedBias(ArrayRef<const DimParams> dimParams,
1208                                                            const BiasGrid&           grid,
1209                                                            ArrayRef<const double> neighborLambdaEnergies,
1210                                                            std::vector<double, AlignedAllocator<double>>* weight) const
1211 {
1212     /* Only neighbors of the current coordinate value will have a non-negligible chance of getting sampled */
1213     const std::vector<int>& neighbors = grid.point(coordState_.gridpointIndex()).neighbor;
1214
1215 #if GMX_SIMD_HAVE_DOUBLE
1216     typedef SimdDouble PackType;
1217     constexpr int      packSize = GMX_SIMD_DOUBLE_WIDTH;
1218 #else
1219     typedef double PackType;
1220     constexpr int  packSize = 1;
1221 #endif
1222     /* Round the size of the weight array up to packSize */
1223     const int weightSize = ((neighbors.size() + packSize - 1) / packSize) * packSize;
1224     weight->resize(weightSize);
1225
1226     double* gmx_restrict weightData = weight->data();
1227     PackType             weightSumPack(0.0);
1228     for (size_t i = 0; i < neighbors.size(); i += packSize)
1229     {
1230         for (size_t n = i; n < i + packSize; n++)
1231         {
1232             if (n < neighbors.size())
1233             {
1234                 const int neighbor = neighbors[n];
1235                 (*weight)[n]       = biasedLogWeightFromPoint(dimParams,
1236                                                         points_,
1237                                                         grid,
1238                                                         neighbor,
1239                                                         points_[neighbor].bias(),
1240                                                         coordState_.coordValue(),
1241                                                         neighborLambdaEnergies,
1242                                                         coordState_.gridpointIndex());
1243             }
1244             else
1245             {
1246                 /* Pad with values that don't affect the result */
1247                 (*weight)[n] = detail::c_largeNegativeExponent;
1248             }
1249         }
1250         PackType weightPack = load<PackType>(weightData + i);
1251         weightPack          = gmx::exp(weightPack);
1252         weightSumPack       = weightSumPack + weightPack;
1253         store(weightData + i, weightPack);
1254     }
1255     /* Sum of probability weights */
1256     double weightSum = reduce(weightSumPack);
1257     GMX_RELEASE_ASSERT(weightSum > 0,
1258                        "zero probability weight when updating AWH probability weights.");
1259
1260     /* Normalize probabilities to sum to 1 */
1261     double invWeightSum = 1 / weightSum;
1262
1263     /* When there is a free energy lambda state axis remove the convolved contributions along that
1264      * axis from the total bias. This must be done after calculating invWeightSum (since weightSum
1265      * will be modified), but before normalizing the weights (below). */
1266     if (grid.hasLambdaAxis())
1267     {
1268         /* If there is only one axis the bias will not be convolved in any dimension. */
1269         if (grid.axis().size() == 1)
1270         {
1271             weightSum = gmx::exp(points_[coordState_.gridpointIndex()].bias());
1272         }
1273         else
1274         {
1275             for (size_t i = 0; i < neighbors.size(); i++)
1276             {
1277                 const int neighbor = neighbors[i];
1278                 if (pointsHaveDifferentLambda(grid, coordState_.gridpointIndex(), neighbor))
1279                 {
1280                     weightSum -= weightData[i];
1281                 }
1282             }
1283         }
1284     }
1285
1286     for (double& w : *weight)
1287     {
1288         w *= invWeightSum;
1289     }
1290
1291     /* Return the convolved bias */
1292     return std::log(weightSum);
1293 }
1294
1295 double BiasState::calcConvolvedBias(ArrayRef<const DimParams> dimParams,
1296                                     const BiasGrid&           grid,
1297                                     const awh_dvec&           coordValue) const
1298 {
1299     int              point     = grid.nearestIndex(coordValue);
1300     const GridPoint& gridPoint = grid.point(point);
1301
1302     /* Sum the probability weights from the neighborhood of the given point */
1303     double weightSum = 0;
1304     for (int neighbor : gridPoint.neighbor)
1305     {
1306         /* No convolution is required along the lambda dimension. */
1307         if (pointsHaveDifferentLambda(grid, point, neighbor))
1308         {
1309             continue;
1310         }
1311         double logWeight = biasedLogWeightFromPoint(
1312                 dimParams, points_, grid, neighbor, points_[neighbor].bias(), coordValue, {}, point);
1313         weightSum += std::exp(logWeight);
1314     }
1315
1316     /* Returns -GMX_FLOAT_MAX if no neighboring points were in the target region. */
1317     return (weightSum > 0) ? std::log(weightSum) : -GMX_FLOAT_MAX;
1318 }
1319
1320 void BiasState::sampleProbabilityWeights(const BiasGrid& grid, gmx::ArrayRef<const double> probWeightNeighbor)
1321 {
1322     const std::vector<int>& neighbor = grid.point(coordState_.gridpointIndex()).neighbor;
1323
1324     /* Save weights for next update */
1325     for (size_t n = 0; n < neighbor.size(); n++)
1326     {
1327         points_[neighbor[n]].increaseWeightSumIteration(probWeightNeighbor[n]);
1328     }
1329
1330     /* Update the local update range. Two corner points define this rectangular
1331      * domain. We need to choose two new corner points such that the new domain
1332      * contains both the old update range and the current neighborhood.
1333      * In the simplest case when an update is performed every sample,
1334      * the update range would simply equal the current neighborhood.
1335      */
1336     int neighborStart = neighbor[0];
1337     int neighborLast  = neighbor[neighbor.size() - 1];
1338     for (int d = 0; d < grid.numDimensions(); d++)
1339     {
1340         int origin = grid.point(neighborStart).index[d];
1341         int last   = grid.point(neighborLast).index[d];
1342
1343         if (origin > last)
1344         {
1345             /* Unwrap if wrapped around the boundary (only happens for periodic
1346              * boundaries). This has been already for the stored index interval.
1347              */
1348             /* TODO: what we want to do is to find the smallest the update
1349              * interval that contains all points that need to be updated.
1350              * This amounts to combining two intervals, the current
1351              * [origin, end] update interval and the new touched neighborhood
1352              * into a new interval that contains all points from both the old
1353              * intervals.
1354              *
1355              * For periodic boundaries it becomes slightly more complicated
1356              * than for closed boundaries because then it needs not be
1357              * true that origin < end (so one can't simply relate the origin/end
1358              * in the min()/max() below). The strategy here is to choose the
1359              * origin closest to a reference point (index 0) and then unwrap
1360              * the end index if needed and choose the largest end index.
1361              * This ensures that both intervals are in the new interval
1362              * but it's not necessarily the smallest.
1363              * Currently we solve this by going through each possibility
1364              * and checking them.
1365              */
1366             last += grid.axis(d).numPointsInPeriod();
1367         }
1368
1369         originUpdatelist_[d] = std::min(originUpdatelist_[d], origin);
1370         endUpdatelist_[d]    = std::max(endUpdatelist_[d], last);
1371     }
1372 }
1373
1374 void BiasState::sampleCoordAndPmf(const std::vector<DimParams>& dimParams,
1375                                   const BiasGrid&               grid,
1376                                   gmx::ArrayRef<const double>   probWeightNeighbor,
1377                                   double                        convolvedBias)
1378 {
1379     /* Sampling-based deconvolution extracting the PMF.
1380      * Update the PMF histogram with the current coordinate value.
1381      *
1382      * Because of the finite width of the harmonic potential, the free energy
1383      * defined for each coordinate point does not exactly equal that of the
1384      * actual coordinate, the PMF. However, the PMF can be estimated by applying
1385      * the relation exp(-PMF) = exp(-bias_convolved)*P_biased/Z, i.e. by keeping a
1386      * reweighted histogram of the coordinate value. Strictly, this relies on
1387      * the unknown normalization constant Z being either constant or known. Here,
1388      * neither is true except in the long simulation time limit. Empirically however,
1389      * it works (mainly because how the PMF histogram is rescaled).
1390      */
1391
1392     const int                gridPointIndex  = coordState_.gridpointIndex();
1393     const std::optional<int> lambdaAxisIndex = grid.lambdaAxisIndex();
1394
1395     /* Update the PMF of points along a lambda axis with their bias. */
1396     if (lambdaAxisIndex)
1397     {
1398         const std::vector<int>& neighbors = grid.point(gridPointIndex).neighbor;
1399
1400         std::vector<double> lambdaMarginalDistribution =
1401                 calculateFELambdaMarginalDistribution(grid, neighbors, probWeightNeighbor);
1402
1403         awh_dvec coordValueAlongLambda = { coordState_.coordValue()[0],
1404                                            coordState_.coordValue()[1],
1405                                            coordState_.coordValue()[2],
1406                                            coordState_.coordValue()[3] };
1407         for (size_t i = 0; i < neighbors.size(); i++)
1408         {
1409             const int neighbor = neighbors[i];
1410             double    bias;
1411             if (pointsAlongLambdaAxis(grid, gridPointIndex, neighbor))
1412             {
1413                 const double neighborLambda = grid.point(neighbor).coordValue[lambdaAxisIndex.value()];
1414                 if (neighbor == gridPointIndex)
1415                 {
1416                     bias = convolvedBias;
1417                 }
1418                 else
1419                 {
1420                     coordValueAlongLambda[lambdaAxisIndex.value()] = neighborLambda;
1421                     bias = calcConvolvedBias(dimParams, grid, coordValueAlongLambda);
1422                 }
1423
1424                 const double probWeight = lambdaMarginalDistribution[neighborLambda];
1425                 const double weightedBias = bias - std::log(std::max(probWeight, GMX_DOUBLE_MIN)); // avoid log(0)
1426
1427                 if (neighbor == gridPointIndex && grid.covers(coordState_.coordValue()))
1428                 {
1429                     points_[neighbor].samplePmf(weightedBias);
1430                 }
1431                 else
1432                 {
1433                     points_[neighbor].updatePmfUnvisited(weightedBias);
1434                 }
1435             }
1436         }
1437     }
1438     else
1439     {
1440         /* Only save coordinate data that is in range (the given index is always
1441          * in range even if the coordinate value is not).
1442          */
1443         if (grid.covers(coordState_.coordValue()))
1444         {
1445             /* Save PMF sum and keep a histogram of the sampled coordinate values */
1446             points_[gridPointIndex].samplePmf(convolvedBias);
1447         }
1448     }
1449
1450     /* Save probability weights for the update */
1451     sampleProbabilityWeights(grid, probWeightNeighbor);
1452 }
1453
1454 void BiasState::initHistoryFromState(AwhBiasHistory* biasHistory) const
1455 {
1456     biasHistory->pointState.resize(points_.size());
1457 }
1458
1459 void BiasState::updateHistory(AwhBiasHistory* biasHistory, const BiasGrid& grid) const
1460 {
1461     GMX_RELEASE_ASSERT(biasHistory->pointState.size() == points_.size(),
1462                        "The AWH history setup does not match the AWH state.");
1463
1464     AwhBiasStateHistory* stateHistory = &biasHistory->state;
1465     stateHistory->umbrellaGridpoint   = coordState_.umbrellaGridpoint();
1466
1467     for (size_t m = 0; m < biasHistory->pointState.size(); m++)
1468     {
1469         AwhPointStateHistory* psh = &biasHistory->pointState[m];
1470
1471         points_[m].storeState(psh);
1472
1473         psh->weightsum_covering = weightSumCovering_[m];
1474     }
1475
1476     histogramSize_.storeState(stateHistory);
1477
1478     stateHistory->origin_index_updatelist = multiDimGridIndexToLinear(grid, originUpdatelist_);
1479     stateHistory->end_index_updatelist    = multiDimGridIndexToLinear(grid, endUpdatelist_);
1480 }
1481
1482 void BiasState::restoreFromHistory(const AwhBiasHistory& biasHistory, const BiasGrid& grid)
1483 {
1484     const AwhBiasStateHistory& stateHistory = biasHistory.state;
1485
1486     coordState_.restoreFromHistory(stateHistory);
1487
1488     if (biasHistory.pointState.size() != points_.size())
1489     {
1490         GMX_THROW(
1491                 InvalidInputError("Bias grid size in checkpoint and simulation do not match. "
1492                                   "Likely you provided a checkpoint from a different simulation."));
1493     }
1494     for (size_t m = 0; m < points_.size(); m++)
1495     {
1496         points_[m].setFromHistory(biasHistory.pointState[m]);
1497     }
1498
1499     for (size_t m = 0; m < weightSumCovering_.size(); m++)
1500     {
1501         weightSumCovering_[m] = biasHistory.pointState[m].weightsum_covering;
1502     }
1503
1504     histogramSize_.restoreFromHistory(stateHistory);
1505
1506     linearGridindexToMultiDim(grid, stateHistory.origin_index_updatelist, originUpdatelist_);
1507     linearGridindexToMultiDim(grid, stateHistory.end_index_updatelist, endUpdatelist_);
1508 }
1509
1510 void BiasState::broadcast(const t_commrec* commRecord)
1511 {
1512     gmx_bcast(sizeof(coordState_), &coordState_, commRecord->mpi_comm_mygroup);
1513
1514     gmx_bcast(points_.size() * sizeof(PointState), points_.data(), commRecord->mpi_comm_mygroup);
1515
1516     gmx_bcast(weightSumCovering_.size() * sizeof(double), weightSumCovering_.data(), commRecord->mpi_comm_mygroup);
1517
1518     gmx_bcast(sizeof(histogramSize_), &histogramSize_, commRecord->mpi_comm_mygroup);
1519 }
1520
1521 void BiasState::setFreeEnergyToConvolvedPmf(ArrayRef<const DimParams> dimParams, const BiasGrid& grid)
1522 {
1523     std::vector<float> convolvedPmf;
1524
1525     calcConvolvedPmf(dimParams, grid, &convolvedPmf);
1526
1527     for (size_t m = 0; m < points_.size(); m++)
1528     {
1529         points_[m].setFreeEnergy(convolvedPmf[m]);
1530     }
1531 }
1532
1533 /*! \brief
1534  * Count trailing data rows containing only zeros.
1535  *
1536  * \param[in] data        2D data array.
1537  * \param[in] numRows     Number of rows in array.
1538  * \param[in] numColumns  Number of cols in array.
1539  * \returns the number of trailing zero rows.
1540  */
1541 static int countTrailingZeroRows(const double* const* data, int numRows, int numColumns)
1542 {
1543     int numZeroRows = 0;
1544     for (int m = numRows - 1; m >= 0; m--)
1545     {
1546         bool rowIsZero = true;
1547         for (int d = 0; d < numColumns; d++)
1548         {
1549             if (data[d][m] != 0)
1550             {
1551                 rowIsZero = false;
1552                 break;
1553             }
1554         }
1555
1556         if (!rowIsZero)
1557         {
1558             /* At a row with non-zero data */
1559             break;
1560         }
1561         else
1562         {
1563             /* Still at a zero data row, keep checking rows higher up. */
1564             numZeroRows++;
1565         }
1566     }
1567
1568     return numZeroRows;
1569 }
1570
1571 /*! \brief
1572  * Initializes the PMF and target with data read from an input table.
1573  *
1574  * \param[in]     dimParams   The dimension parameters.
1575  * \param[in]     grid        The grid.
1576  * \param[in]     filename    The filename to read PMF and target from.
1577  * \param[in]     numBias     Number of biases.
1578  * \param[in]     biasIndex   The index of the bias.
1579  * \param[in,out] pointState  The state of the points in this bias.
1580  */
1581 static void readUserPmfAndTargetDistribution(ArrayRef<const DimParams> dimParams,
1582                                              const BiasGrid&           grid,
1583                                              const std::string&        filename,
1584                                              int                       numBias,
1585                                              int                       biasIndex,
1586                                              std::vector<PointState>*  pointState)
1587 {
1588     /* Read the PMF and target distribution.
1589        From the PMF, the convolved PMF, or the reference value free energy, can be calculated
1590        base on the force constant. The free energy and target together determine the bias.
1591      */
1592     std::string filenameModified(filename);
1593     if (numBias > 1)
1594     {
1595         size_t n = filenameModified.rfind('.');
1596         GMX_RELEASE_ASSERT(n != std::string::npos,
1597                            "The filename should contain an extension starting with .");
1598         filenameModified.insert(n, formatString("%d", biasIndex));
1599     }
1600
1601     std::string correctFormatMessage = formatString(
1602             "%s is expected in the following format. "
1603             "The first ndim column(s) should contain the coordinate values for each point, "
1604             "each column containing values of one dimension (in ascending order). "
1605             "For a multidimensional coordinate, points should be listed "
1606             "in the order obtained by traversing lower dimensions first. "
1607             "E.g. for two-dimensional grid of size nxn: "
1608             "(1, 1), (1, 2),..., (1, n), (2, 1), (2, 2), ..., , (n, n - 1), (n, n). "
1609             "Column ndim +  1 should contain the PMF value for each coordinate value. "
1610             "The target distribution values should be in column ndim + 2  or column ndim + 5. "
1611             "Make sure the input file ends with a new line but has no trailing new lines.",
1612             filename.c_str());
1613     gmx::TextLineWrapper wrapper;
1614     wrapper.settings().setLineLength(c_linewidth);
1615     correctFormatMessage = wrapper.wrapToString(correctFormatMessage);
1616
1617     double** data;
1618     int      numColumns;
1619     int      numRows = read_xvg(filenameModified.c_str(), &data, &numColumns);
1620
1621     /* Check basic data properties here. BiasGrid takes care of more complicated things. */
1622
1623     if (numRows <= 0)
1624     {
1625         std::string mesg = gmx::formatString(
1626                 "%s is empty!.\n\n%s", filename.c_str(), correctFormatMessage.c_str());
1627         GMX_THROW(InvalidInputError(mesg));
1628     }
1629
1630     /* Less than 2 points is not useful for PMF or target. */
1631     if (numRows < 2)
1632     {
1633         std::string mesg = gmx::formatString(
1634                 "%s contains too few data points (%d)."
1635                 "The minimum number of points is 2.",
1636                 filename.c_str(),
1637                 numRows);
1638         GMX_THROW(InvalidInputError(mesg));
1639     }
1640
1641     /* Make sure there are enough columns of data.
1642
1643        Two formats are allowed. Either with columns  {coords, PMF, target} or
1644        {coords, PMF, x, y, z, target, ...}. The latter format is allowed since that
1645        is how AWH output is written (x, y, z being other AWH variables). For this format,
1646        trailing columns are ignored.
1647      */
1648     int columnIndexTarget;
1649     int numColumnsMin  = dimParams.size() + 2;
1650     int columnIndexPmf = dimParams.size();
1651     if (numColumns == numColumnsMin)
1652     {
1653         columnIndexTarget = columnIndexPmf + 1;
1654     }
1655     else
1656     {
1657         columnIndexTarget = columnIndexPmf + 4;
1658     }
1659
1660     if (numColumns < numColumnsMin)
1661     {
1662         std::string mesg = gmx::formatString(
1663                 "The number of columns in %s should be at least %d."
1664                 "\n\n%s",
1665                 filename.c_str(),
1666                 numColumnsMin,
1667                 correctFormatMessage.c_str());
1668         GMX_THROW(InvalidInputError(mesg));
1669     }
1670
1671     /* read_xvg can give trailing zero data rows for trailing new lines in the input. We allow 1 zero row,
1672        since this could be real data. But multiple trailing zero rows cannot correspond to valid data. */
1673     int numZeroRows = countTrailingZeroRows(data, numRows, numColumns);
1674     if (numZeroRows > 1)
1675     {
1676         std::string mesg = gmx::formatString(
1677                 "Found %d trailing zero data rows in %s. Please remove trailing empty lines and "
1678                 "try again.",
1679                 numZeroRows,
1680                 filename.c_str());
1681         GMX_THROW(InvalidInputError(mesg));
1682     }
1683
1684     /* Convert from user units to internal units before sending the data of to grid. */
1685     for (size_t d = 0; d < dimParams.size(); d++)
1686     {
1687         double scalingFactor = dimParams[d].scaleUserInputToInternal(1);
1688         if (scalingFactor == 1)
1689         {
1690             continue;
1691         }
1692         for (size_t m = 0; m < pointState->size(); m++)
1693         {
1694             data[d][m] *= scalingFactor;
1695         }
1696     }
1697
1698     /* Get a data point for each AWH grid point so that they all get data. */
1699     std::vector<int> gridIndexToDataIndex(grid.numPoints());
1700     mapGridToDataGrid(&gridIndexToDataIndex, data, numRows, filename, grid, correctFormatMessage);
1701
1702     /* Extract the data for each grid point.
1703      * We check if the target distribution is zero for all points.
1704      */
1705     bool targetDistributionIsZero = true;
1706     for (size_t m = 0; m < pointState->size(); m++)
1707     {
1708         (*pointState)[m].setLogPmfSum(-data[columnIndexPmf][gridIndexToDataIndex[m]]);
1709         double target = data[columnIndexTarget][gridIndexToDataIndex[m]];
1710
1711         /* Check if the values are allowed. */
1712         if (target < 0)
1713         {
1714             std::string mesg = gmx::formatString(
1715                     "Target distribution weight at point %zu (%g) in %s is negative.",
1716                     m,
1717                     target,
1718                     filename.c_str());
1719             GMX_THROW(InvalidInputError(mesg));
1720         }
1721         if (target > 0)
1722         {
1723             targetDistributionIsZero = false;
1724         }
1725         (*pointState)[m].setTargetConstantWeight(target);
1726     }
1727
1728     if (targetDistributionIsZero)
1729     {
1730         std::string mesg =
1731                 gmx::formatString("The target weights given in column %d in %s are all 0",
1732                                   columnIndexTarget,
1733                                   filename.c_str());
1734         GMX_THROW(InvalidInputError(mesg));
1735     }
1736
1737     /* Free the arrays. */
1738     for (int m = 0; m < numColumns; m++)
1739     {
1740         sfree(data[m]);
1741     }
1742     sfree(data);
1743 }
1744
1745 void BiasState::normalizePmf(int numSharingSims)
1746 {
1747     /* The normalization of the PMF estimate matters because it determines how big effect the next sample has.
1748        Approximately (for large enough force constant) we should have:
1749        sum_x(exp(-pmf(x)) = nsamples*sum_xref(exp(-f(xref)).
1750      */
1751
1752     /* Calculate the normalization factor, i.e. divide by the pmf sum, multiply by the number of samples and the f sum */
1753     double expSumPmf = 0;
1754     double expSumF   = 0;
1755     for (const PointState& pointState : points_)
1756     {
1757         if (pointState.inTargetRegion())
1758         {
1759             expSumPmf += std::exp(pointState.logPmfSum());
1760             expSumF += std::exp(-pointState.freeEnergy());
1761         }
1762     }
1763     double numSamples = histogramSize_.histogramSize() / numSharingSims;
1764
1765     /* Renormalize */
1766     double logRenorm = std::log(numSamples * expSumF / expSumPmf);
1767     for (PointState& pointState : points_)
1768     {
1769         if (pointState.inTargetRegion())
1770         {
1771             pointState.setLogPmfSum(pointState.logPmfSum() + logRenorm);
1772         }
1773     }
1774 }
1775
1776 void BiasState::initGridPointState(const AwhBiasParams&      awhBiasParams,
1777                                    ArrayRef<const DimParams> dimParams,
1778                                    const BiasGrid&           grid,
1779                                    const BiasParams&         params,
1780                                    const std::string&        filename,
1781                                    int                       numBias)
1782 {
1783     /* Modify PMF, free energy and the constant target distribution factor
1784      * to user input values if there is data given.
1785      */
1786     if (awhBiasParams.userPMFEstimate())
1787     {
1788         readUserPmfAndTargetDistribution(dimParams, grid, filename, numBias, params.biasIndex, &points_);
1789         setFreeEnergyToConvolvedPmf(dimParams, grid);
1790     }
1791
1792     /* The local Boltzmann distribution is special because the target distribution is updated as a function of the reference weighthistogram. */
1793     GMX_RELEASE_ASSERT(params.eTarget != AwhTargetType::LocalBoltzmann || points_[0].weightSumRef() != 0,
1794                        "AWH reference weight histogram not initialized properly with local "
1795                        "Boltzmann target distribution.");
1796
1797     updateTargetDistribution(points_, params);
1798
1799     for (PointState& pointState : points_)
1800     {
1801         if (pointState.inTargetRegion())
1802         {
1803             pointState.updateBias();
1804         }
1805         else
1806         {
1807             /* Note that for zero target this is a value that represents -infinity but should not be used for biasing. */
1808             pointState.setTargetToZero();
1809         }
1810     }
1811
1812     /* Set the initial reference weighthistogram. */
1813     const double histogramSize = histogramSize_.histogramSize();
1814     for (auto& pointState : points_)
1815     {
1816         pointState.setInitialReferenceWeightHistogram(histogramSize);
1817     }
1818
1819     /* Make sure the pmf is normalized consistently with the histogram size.
1820        Note: the target distribution and free energy need to be set here. */
1821     normalizePmf(params.numSharedUpdate);
1822 }
1823
1824 BiasState::BiasState(const AwhBiasParams&      awhBiasParams,
1825                      double                    histogramSizeInitial,
1826                      ArrayRef<const DimParams> dimParams,
1827                      const BiasGrid&           grid,
1828                      const BiasSharing*        biasSharing) :
1829     coordState_(awhBiasParams, dimParams, grid),
1830     points_(grid.numPoints()),
1831     weightSumCovering_(grid.numPoints()),
1832     histogramSize_(awhBiasParams, histogramSizeInitial),
1833     biasSharing_(biasSharing)
1834 {
1835     /* The minimum and maximum multidimensional point indices that are affected by the next update */
1836     for (size_t d = 0; d < dimParams.size(); d++)
1837     {
1838         int index            = grid.point(coordState_.gridpointIndex()).index[d];
1839         originUpdatelist_[d] = index;
1840         endUpdatelist_[d]    = index;
1841     }
1842 }
1843
1844 } // namespace gmx