78b84e95e88f21e50dcd8a1923368694bcdb4150
[alexxy/gromacs.git] / src / gromacs / applied_forces / awh / tests / bias_fep_lambda_state.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 #include "gmxpre.h"
36
37 #include <cmath>
38
39 #include <memory>
40 #include <random>
41 #include <tuple>
42 #include <vector>
43
44 #include <gmock/gmock.h>
45 #include <gtest/gtest.h>
46
47 #include "gromacs/applied_forces/awh/bias.h"
48 #include "gromacs/applied_forces/awh/correlationgrid.h"
49 #include "gromacs/applied_forces/awh/pointstate.h"
50 #include "gromacs/mdtypes/awh_params.h"
51 #include "gromacs/utility/stringutil.h"
52
53 #include "testutils/refdata.h"
54 #include "testutils/testasserts.h"
55
56 namespace gmx
57 {
58
59 namespace test
60 {
61
62 //! The number of lambda states to use in the tests.
63 const int numLambdaStates = 16;
64
65 /*! \internal \brief
66  * Struct that gathers all input for setting up and using a Bias
67  */
68 struct AwhFepLambdaStateTestParameters
69 {
70     AwhFepLambdaStateTestParameters() = default;
71     //! Move constructor
72     AwhFepLambdaStateTestParameters(AwhFepLambdaStateTestParameters&& o) noexcept :
73         beta(o.beta),
74         awhDimParams(o.awhDimParams),
75         awhBiasParams(o.awhBiasParams),
76         awhParams(o.awhParams),
77         dimParams(std::move(o.dimParams))
78     {
79         awhBiasParams.dimParams = &awhDimParams;
80         awhParams.awhBiasParams = &awhBiasParams;
81     }
82     double beta; //!< 1/(kB*T)
83
84     AwhDimParams  awhDimParams;  //!< Dimension parameters pointed to by \p awhBiasParams
85     AwhBiasParams awhBiasParams; //!< Bias parameters pointed to by \[ awhParams
86     AwhParams     awhParams;     //!< AWH parameters, this is the struct to actually use
87
88     std::vector<DimParams> dimParams; //!< Dimension parameters for setting up Bias
89 };
90
91 /*! \internal \brief Helper function to fill an array with random values (between lowerBound and
92  * upperBound) from randomEngine.
93  */
94 static void randomArrayFill(ArrayRef<double>           array,
95                             std::default_random_engine randomEngine,
96                             double                     lowerBound,
97                             double                     upperBound)
98 {
99     std::uniform_real_distribution<double> unif(lowerBound, upperBound);
100     for (size_t i = 0; i < array.size(); i++)
101     {
102         array[i] = unif(randomEngine);
103     }
104 }
105
106 //! Helper function to set up the C-style AWH parameters for the test
107 static AwhFepLambdaStateTestParameters getAwhTestParameters(int eawhgrowth, int eawhpotential)
108 {
109     AwhFepLambdaStateTestParameters params;
110
111     params.beta = 0.4;
112
113     AwhDimParams& awhDimParams = params.awhDimParams;
114
115     awhDimParams.period         = 0;
116     awhDimParams.diffusion      = 1e-4;
117     awhDimParams.origin         = 0;
118     awhDimParams.end            = numLambdaStates - 1;
119     awhDimParams.coordValueInit = awhDimParams.origin;
120     awhDimParams.coverDiameter  = 0;
121     awhDimParams.eCoordProvider = eawhcoordproviderFREE_ENERGY_LAMBDA;
122
123     AwhBiasParams& awhBiasParams = params.awhBiasParams;
124
125     awhBiasParams.ndim                 = 1;
126     awhBiasParams.dimParams            = &awhDimParams;
127     awhBiasParams.eTarget              = eawhtargetCONSTANT;
128     awhBiasParams.targetBetaScaling    = 0;
129     awhBiasParams.targetCutoff         = 0;
130     awhBiasParams.eGrowth              = eawhgrowth;
131     awhBiasParams.bUserData            = FALSE;
132     awhBiasParams.errorInitial         = 1.0 / params.beta;
133     awhBiasParams.shareGroup           = 0;
134     awhBiasParams.equilibrateHistogram = FALSE;
135
136     double  k    = 1000;
137     int64_t seed = 93471803;
138
139     params.dimParams.emplace_back(k, params.beta, numLambdaStates);
140
141     AwhParams& awhParams = params.awhParams;
142
143     awhParams.numBias                    = 1;
144     awhParams.awhBiasParams              = &awhBiasParams;
145     awhParams.seed                       = seed;
146     awhParams.nstOut                     = 0;
147     awhParams.nstSampleCoord             = 1;
148     awhParams.numSamplesUpdateFreeEnergy = 10;
149     awhParams.ePotential                 = eawhpotential;
150     awhParams.shareBiasMultisim          = FALSE;
151
152     return params;
153 }
154
155 //! Convenience typedef: growth type enum, potential type enum, disable update skips
156 typedef std::tuple<int, int, BiasParams::DisableUpdateSkips> BiasTestParameters;
157
158 /*! \brief Test fixture for testing Bias updates
159  */
160 class BiasFepLambdaStateTest : public ::testing::TestWithParam<BiasTestParameters>
161 {
162 public:
163     //! Random seed for AWH MC sampling
164     int64_t seed_;
165
166     //! The awh Bias
167     std::unique_ptr<Bias> bias_;
168
169     BiasFepLambdaStateTest()
170     {
171         /* We test all combinations of:
172          *   eawhgrowth:
173          *     eawhgrowthLINEAR:     final, normal update phase
174          *     ewahgrowthEXP_LINEAR: intial phase, updated size is constant
175          *   eawhpotential (test both, but for the FEP lambda state dimension MC will in practice be used,
176          *                  except that eawhpotentialCONVOLVED also gives a potential output):
177          *     eawhpotentialUMBRELLA:  MC on lambda state
178          *     eawhpotentialCONVOLVED: MD on a convolved potential landscape (falling back to MC on lambda state)
179          *   disableUpdateSkips (should not affect the results):
180          *     BiasParams::DisableUpdateSkips::yes: update the point state for every sample
181          *     BiasParams::DisableUpdateSkips::no:  update the point state at an interval > 1 sample
182          *
183          * Note: It would be nice to explicitly check that eawhpotential
184          *       and disableUpdateSkips do not affect the point state.
185          *       But the reference data will also ensure this.
186          */
187         int                            eawhgrowth;
188         int                            eawhpotential;
189         BiasParams::DisableUpdateSkips disableUpdateSkips;
190         std::tie(eawhgrowth, eawhpotential, disableUpdateSkips) = GetParam();
191
192         /* Set up a basic AWH setup with a single, 1D bias with parameters
193          * such that we can measure the effects of different parameters.
194          */
195         const AwhFepLambdaStateTestParameters params = getAwhTestParameters(eawhgrowth, eawhpotential);
196
197         seed_ = params.awhParams.seed;
198
199         double mdTimeStep = 0.1;
200
201         bias_ = std::make_unique<Bias>(-1, params.awhParams, params.awhBiasParams, params.dimParams,
202                                        params.beta, mdTimeStep, 1, "", Bias::ThisRankWillDoIO::No,
203                                        disableUpdateSkips);
204     }
205 };
206
207 TEST_P(BiasFepLambdaStateTest, ForcesBiasPmf)
208 {
209     gmx::test::TestReferenceData    data;
210     gmx::test::TestReferenceChecker checker(data.rootChecker());
211
212     Bias& bias = *bias_;
213
214     /* Make strings with the properties we expect to be different in the tests.
215      * These also helps to interpret the reference data.
216      */
217     std::vector<std::string> props;
218     props.push_back(formatString("stage:           %s", bias.state().inInitialStage() ? "initial" : "final"));
219     props.push_back(formatString("convolve forces: %s", bias.params().convolveForce ? "yes" : "no"));
220     props.push_back(formatString("skip updates:    %s", bias.params().skipUpdates() ? "yes" : "no"));
221
222     SCOPED_TRACE(gmx::formatString("%s, %s, %s", props[0].c_str(), props[1].c_str(), props[2].c_str()));
223
224     std::vector<double> force, pot;
225
226     double                     potentialJump        = 0;
227     double                     mdTimeStep           = 0.1;
228     double                     energyNoiseMagnitude = 1.0;
229     double                     dhdlNoiseMagnitude   = 1.5;
230     int                        nSteps               = 501;
231     std::default_random_engine randomEngine;
232     randomEngine.seed(1234);
233
234     /* Some energies to use as base values (to which some noise is added later on). */
235     std::vector<double> lambdaEnergyBase(numLambdaStates);
236     std::vector<double> lambdaDhdlBase(numLambdaStates);
237     const double        magnitude = 12.0;
238     for (int i = 0; i < numLambdaStates; i++)
239     {
240         lambdaEnergyBase[i] = magnitude * std::sin(i * 0.1);
241         lambdaDhdlBase[i]   = magnitude * std::cos(i * 0.1);
242     }
243
244     for (int step = 0; step < nSteps; step++)
245     {
246         /* Create some noise and add it to the base values */
247         std::vector<double> neighborLambdaEnergyNoise(numLambdaStates);
248         std::vector<double> neighborLambdaDhdlNoise(numLambdaStates);
249         randomArrayFill(neighborLambdaEnergyNoise, randomEngine, -energyNoiseMagnitude, energyNoiseMagnitude);
250         randomArrayFill(neighborLambdaDhdlNoise, randomEngine, -dhdlNoiseMagnitude, dhdlNoiseMagnitude);
251         std::vector<double> neighborLambdaEnergies(numLambdaStates);
252         std::vector<double> neighborLambdaDhdl(numLambdaStates);
253         for (int i = 0; i < numLambdaStates; i++)
254         {
255             neighborLambdaEnergies[i] = lambdaEnergyBase[i] + neighborLambdaEnergyNoise[i];
256             neighborLambdaDhdl[i]     = lambdaDhdlBase[i] + neighborLambdaDhdlNoise[i];
257         }
258
259         int      umbrellaGridpointIndex = bias.state().coordState().umbrellaGridpoint();
260         awh_dvec coordValue = { bias.getGridCoordValue(umbrellaGridpointIndex)[0], 0, 0, 0 };
261         double   potential  = 0;
262         gmx::ArrayRef<const double> biasForce = bias.calcForceAndUpdateBias(
263                 coordValue, neighborLambdaEnergies, neighborLambdaDhdl, &potential, &potentialJump,
264                 nullptr, nullptr, step * mdTimeStep, step, seed_, nullptr);
265
266         force.push_back(biasForce[0]);
267         pot.push_back(potential);
268     }
269
270     /* When skipping updates, ensure all skipped updates are performed here.
271      * This should result in the same bias state as at output in a normal run.
272      */
273     if (bias.params().skipUpdates())
274     {
275         bias.doSkippedUpdatesForAllPoints();
276     }
277
278     std::vector<double> pointBias, logPmfsum;
279     for (auto& point : bias.state().points())
280     {
281         pointBias.push_back(point.bias());
282         logPmfsum.push_back(point.logPmfSum());
283     }
284
285     constexpr int ulpTol = 10;
286
287     checker.checkSequence(props.begin(), props.end(), "Properties");
288     checker.setDefaultTolerance(absoluteTolerance(magnitude * GMX_DOUBLE_EPS * ulpTol));
289     checker.checkSequence(force.begin(), force.end(), "Force");
290     checker.checkSequence(pot.begin(), pot.end(), "Potential");
291     checker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpTol));
292     checker.checkSequence(pointBias.begin(), pointBias.end(), "PointBias");
293     checker.checkSequence(logPmfsum.begin(), logPmfsum.end(), "PointLogPmfsum");
294 }
295
296 /* Scan initial/final phase, MC/convolved force and update skip (not) allowed
297  * Both the convolving and skipping should not affect the bias and PMF.
298  * It would be nice if the test would explicitly check for this.
299  * Currently this is tested through identical reference data.
300  */
301 INSTANTIATE_TEST_CASE_P(WithParameters,
302                         BiasFepLambdaStateTest,
303                         ::testing::Combine(::testing::Values(eawhgrowthLINEAR, eawhgrowthEXP_LINEAR),
304                                            ::testing::Values(eawhpotentialUMBRELLA, eawhpotentialCONVOLVED),
305                                            ::testing::Values(BiasParams::DisableUpdateSkips::yes,
306                                                              BiasParams::DisableUpdateSkips::no)));
307
308 // Test that we detect coverings and exit the initial stage at the correct step
309 TEST(BiasFepLambdaStateTest, DetectsCovering)
310 {
311     const AwhFepLambdaStateTestParameters params =
312             getAwhTestParameters(eawhgrowthEXP_LINEAR, eawhpotentialCONVOLVED);
313
314     const double mdTimeStep = 0.1;
315
316     Bias bias(-1, params.awhParams, params.awhBiasParams, params.dimParams, params.beta, mdTimeStep,
317               1, "", Bias::ThisRankWillDoIO::No);
318
319     const int64_t exitStepRef = 380;
320
321     bool inInitialStage = bias.state().inInitialStage();
322
323     double                     energyNoiseMagnitude = 1.0;
324     double                     dhdlNoiseMagnitude   = 1.5;
325     std::default_random_engine randomEngine;
326     randomEngine.seed(1234);
327
328     /* Some energies to use as base values (to which some noise is added later on). */
329     std::vector<double> lambdaEnergyBase(numLambdaStates);
330     std::vector<double> lambdaDhdlBase(numLambdaStates);
331     const double        magnitude = 12.0;
332     for (int i = 0; i < numLambdaStates; i++)
333     {
334         lambdaEnergyBase[i] = magnitude * std::sin(i * 0.1);
335         lambdaDhdlBase[i]   = magnitude * std::cos(i * 0.1);
336     }
337
338     int64_t step;
339     /* Normally this loop exits at exitStepRef, but we extend with failure */
340     for (step = 0; step <= 2 * exitStepRef; step++)
341     {
342         /* Create some noise and add it to the base values */
343         std::vector<double> neighborLambdaEnergyNoise(numLambdaStates);
344         std::vector<double> neighborLambdaDhdlNoise(numLambdaStates);
345         randomArrayFill(neighborLambdaEnergyNoise, randomEngine, -energyNoiseMagnitude, energyNoiseMagnitude);
346         randomArrayFill(neighborLambdaDhdlNoise, randomEngine, -dhdlNoiseMagnitude, dhdlNoiseMagnitude);
347         std::vector<double> neighborLambdaEnergies(numLambdaStates);
348         std::vector<double> neighborLambdaDhdl(numLambdaStates);
349         for (int i = 0; i < numLambdaStates; i++)
350         {
351             neighborLambdaEnergies[i] = lambdaEnergyBase[i] + neighborLambdaEnergyNoise[i];
352             neighborLambdaDhdl[i]     = lambdaDhdlBase[i] + neighborLambdaDhdlNoise[i];
353         }
354
355         int      umbrellaGridpointIndex = bias.state().coordState().umbrellaGridpoint();
356         awh_dvec coordValue = { bias.getGridCoordValue(umbrellaGridpointIndex)[0], 0, 0, 0 };
357
358         double potential     = 0;
359         double potentialJump = 0;
360         bias.calcForceAndUpdateBias(coordValue, neighborLambdaEnergies, neighborLambdaDhdl,
361                                     &potential, &potentialJump, nullptr, nullptr, step, step,
362                                     params.awhParams.seed, nullptr);
363
364         inInitialStage = bias.state().inInitialStage();
365         if (!inInitialStage)
366         {
367             break;
368         }
369     }
370
371     EXPECT_EQ(false, inInitialStage);
372     if (!inInitialStage)
373     {
374         EXPECT_EQ(exitStepRef, step);
375     }
376 }
377
378 } // namespace test
379 } // namespace gmx