Improve stability of ContinuationIsExact tests
[alexxy/gromacs.git] / src / programs / mdrun / tests / exactcontinuation.cpp
index 1a68fb53094a0820d557219885efd1cd1e11180e..8b4b8f051faa60be5bc2fb079a946f5e0db0fb92 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2018,2019, by the GROMACS development team, led by
+ * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
  * and including many others, as listed in the AUTHORS file in the
  * top-level source directory and at http://www.gromacs.org.
@@ -87,152 +87,156 @@ namespace
  *
  * \tparam FrameReader  Has readNextFrame() and frame() methods
  *                      useful for returning successive Frame objects */
-template <class FrameReader>
+template<class FrameReader>
 class ContinuationFramePairManager
 {
-    public:
-        //! Convenience typedef
-        typedef std::unique_ptr<FrameReader> FrameReaderPtr;
-        //! Constructor
-        ContinuationFramePairManager(FrameReaderPtr full,
-                                     FrameReaderPtr firstPart,
-                                     FrameReaderPtr secondPart) :
-            full_(std::move(full)),
-            firstPart_(std::move(firstPart)),
-            secondPart_(std::move(secondPart)),
-            isFirstPart_(true)
-        {}
-        /*! \brief Probe for a pair of valid frames, and return true if both are found.
-         *
-         * Gives a test failure if exactly one frame is found, because
-         * it is an error for either run to have missing or extra
-         * frames.  Note that the frame where the two-part run ends
-         * and begins is duplicated between the two output files by
-         * mdrun, and the test accommodates this.
-         *
-         * \todo This would be straightforward if velocity Verlet
-         * behaved like other integrators. */
-        bool shouldContinueComparing()
+public:
+    //! Convenience typedef
+    typedef std::unique_ptr<FrameReader> FrameReaderPtr;
+    //! Constructor
+    ContinuationFramePairManager(FrameReaderPtr full, FrameReaderPtr firstPart, FrameReaderPtr secondPart) :
+        full_(std::move(full)),
+        firstPart_(std::move(firstPart)),
+        secondPart_(std::move(secondPart)),
+        isFirstPart_(true)
+    {
+    }
+    /*! \brief Probe for a pair of valid frames, and return true if both are found.
+     *
+     * Gives a test failure if exactly one frame is found, because
+     * it is an error for either run to have missing or extra
+     * frames.  Note that the frame where the two-part run ends
+     * and begins is duplicated between the two output files by
+     * mdrun, and the test accommodates this.
+     *
+     * \todo This would be straightforward if velocity Verlet
+     * behaved like other integrators. */
+    bool shouldContinueComparing()
+    {
+        if (full_->readNextFrame())
         {
-            if (full_->readNextFrame())
+            if (isFirstPart_)
             {
-                if (isFirstPart_)
+                if (firstPart_->readNextFrame())
                 {
-                    if (firstPart_->readNextFrame())
-                    {
-                        // Two valid next frames exist, so we should continue comparing.
-                        return true;
-                    }
-                    else
+                    // Two valid next frames exist, so we should continue comparing.
+                    return true;
+                }
+                else
+                {
+                    // First part ran out of frames, move on to the second part
+                    isFirstPart_ = false;
+                    if (secondPart_->readNextFrame())
                     {
-                        // First part ran out of frames, move on to the second part
-                        isFirstPart_  = false;
+                        // Skip a second-part frame so the one we will
+                        // read can compare with the next full-run
+                        // frames.
+                        secondPart_->frame();
                         if (secondPart_->readNextFrame())
                         {
-                            // Skip a second-part frame so the one we will
-                            // read can compare with the next full-run
-                            // frames.
-                            secondPart_->frame();
-                            if (secondPart_->readNextFrame())
-                            {
-                                // Two valid next frames exist, so we should continue comparing.
-                                return true;
-                            }
-                            else
-                            {
-                                ADD_FAILURE() << "Second-part energy file had no (new) frames";
-                            }
+                            // Two valid next frames exist, so we should continue comparing.
+                            return true;
                         }
                         else
                         {
-                            ADD_FAILURE() << "Second-part energy file had no frames";
+                            ADD_FAILURE() << "Second-part energy file had no (new) frames";
                         }
                     }
-                }
-                else
-                {
-                    if (secondPart_->readNextFrame())
-                    {
-                        // Two valid next frames exist, so we should continue comparing.
-                        return true;
-                    }
                     else
                     {
-                        ADD_FAILURE() << "Full run energy file had at least one more frame than two-part run energy file";
+                        ADD_FAILURE() << "Second-part energy file had no frames";
                     }
                 }
             }
             else
             {
-                if (isFirstPart_)
+                if (secondPart_->readNextFrame())
                 {
-                    ADD_FAILURE() << "Full-run energy file ran out of frames before the first part of the two-part run completed";
+                    // Two valid next frames exist, so we should continue comparing.
+                    return true;
                 }
                 else
                 {
-                    if (secondPart_->readNextFrame())
-                    {
-                        ADD_FAILURE() << "Two-part run energy file had at least one more frame than full-run energy file";
-                    }
-                    else
-                    {
-                        // Both files ran out of frames at the same time, which is the expected behaviour.
-                    }
+                    ADD_FAILURE() << "Full run energy file had at least one more frame than "
+                                     "two-part run energy file";
                 }
             }
-            // At least one file is out of frames, so should not continue comparing.
-            return false;
         }
-        /*! \brief Compare all possible pairs of frames using \c compareTwoFrames.
-         *
-         * \tparam Frame  The type of frame used in the comparison (returned
-         *                by FrameReader and used by compareTwoFrames). */
-        template <class Frame>
-        void compareAllFramePairs(std::function<void(const Frame &, const Frame &)> compareTwoFrames)
+        else
         {
-            while (shouldContinueComparing())
+            if (isFirstPart_)
             {
-                EnergyFrame firstFrame  = full_->frame();
-                EnergyFrame secondFrame = isFirstPart_ ? firstPart_->frame() : secondPart_->frame();
-                SCOPED_TRACE("Comparing frames from two runs '" + firstFrame.frameName() + "' and '" + secondFrame.frameName() + "'");
-                compareTwoFrames(firstFrame, secondFrame);
+                ADD_FAILURE() << "Full-run energy file ran out of frames before the first part of "
+                                 "the two-part run completed";
             }
-
+            else
+            {
+                if (secondPart_->readNextFrame())
+                {
+                    ADD_FAILURE() << "Two-part run energy file had at least one more frame than "
+                                     "full-run energy file";
+                }
+                else
+                {
+                    // Both files ran out of frames at the same time, which is the expected behaviour.
+                }
+            }
+        }
+        // At least one file is out of frames, so should not continue comparing.
+        return false;
+    }
+    /*! \brief Compare all possible pairs of frames using \c compareTwoFrames.
+     *
+     * \tparam Frame  The type of frame used in the comparison (returned
+     *                by FrameReader and used by compareTwoFrames). */
+    template<class Frame>
+    void compareAllFramePairs(std::function<void(const Frame&, const Frame&)> compareTwoFrames)
+    {
+        while (shouldContinueComparing())
+        {
+            EnergyFrame firstFrame  = full_->frame();
+            EnergyFrame secondFrame = isFirstPart_ ? firstPart_->frame() : secondPart_->frame();
+            SCOPED_TRACE("Comparing frames from two runs '" + firstFrame.frameName() + "' and '"
+                         + secondFrame.frameName() + "'");
+            compareTwoFrames(firstFrame, secondFrame);
         }
+    }
 
-    private:
-        EnergyFrameReaderPtr full_;
-        EnergyFrameReaderPtr firstPart_;
-        EnergyFrameReaderPtr secondPart_;
-        bool                 isFirstPart_;
+private:
+    EnergyFrameReaderPtr full_;
+    EnergyFrameReaderPtr firstPart_;
+    EnergyFrameReaderPtr secondPart_;
+    bool                 isFirstPart_;
 };
 
 /*! \brief Run grompp for a normal mdrun, the same mdrun stopping part
  * way, doing a continuation, and compare the results. */
-void runTest(TestFileManager            *fileManager,
-             SimulationRunner           *runner,
-             const std::string          &simulationName,
+void runTest(TestFileManager*            fileManager,
+             SimulationRunner*           runner,
+             const std::string&          simulationName,
              int                         maxWarningsTolerated,
-             const MdpFieldValues       &mdpFieldValues,
-             const EnergyTermsToCompare &energyTermsToCompare)
+             const MdpFieldValues&       mdpFieldValues,
+             const EnergyTermsToCompareenergyTermsToCompare)
 {
     int numRanksAvailable = getNumberOfTestMpiRanks();
     if (!isNumberOfPpRanksSupported(simulationName, numRanksAvailable))
     {
-        fprintf(stdout, "Test system '%s' cannot run with %d ranks.\n"
+        fprintf(stdout,
+                "Test system '%s' cannot run with %d ranks.\n"
                 "The supported numbers are: %s\n",
-                simulationName.c_str(), numRanksAvailable,
+                simulationName.c_str(),
+                numRanksAvailable,
                 reportNumbersOfPpRanksSupported(simulationName).c_str());
         return;
     }
 
     // prepare some names for files to use with the two mdrun calls
-    std::string fullRunTprFileName              = fileManager->getTemporaryFilePath("full.tpr");
-    std::string firstPartRunTprFileName         = fileManager->getTemporaryFilePath("firstpart.tpr");
-    std::string fullRunEdrFileName              = fileManager->getTemporaryFilePath("full.edr");
-    std::string firstPartRunEdrFileName         = fileManager->getTemporaryFilePath("firstpart.edr");
-    std::string firstPartRunCheckpointFileName  = fileManager->getTemporaryFilePath("firstpart.cpt");
-    std::string secondPartRunEdrFileName        = fileManager->getTemporaryFilePath("secondpart");
+    std::string fullRunTprFileName             = fileManager->getTemporaryFilePath("full.tpr");
+    std::string firstPartRunTprFileName        = fileManager->getTemporaryFilePath("firstpart.tpr");
+    std::string fullRunEdrFileName             = fileManager->getTemporaryFilePath("full.edr");
+    std::string firstPartRunEdrFileName        = fileManager->getTemporaryFilePath("firstpart.edr");
+    std::string firstPartRunCheckpointFileName = fileManager->getTemporaryFilePath("firstpart.cpt");
+    std::string secondPartRunEdrFileName       = fileManager->getTemporaryFilePath("secondpart");
 
     // prepare the full run .tpr file, which will be used for the full
     // run, and for the second part of the two-part run.
@@ -248,6 +252,8 @@ void runTest(TestFileManager            *fileManager,
         EXPECT_EQ(0, runner->callGrompp(caller));
     }
 
+    const std::string splitPoint = std::to_string(std::stoi(mdpFieldValues.at("nsteps")) / 2);
+
     // prepare the .tpr file for the first part of the two-part run
     {
         // TODO evolve grompp to report the number of warnings issued, so
@@ -256,8 +262,8 @@ void runTest(TestFileManager            *fileManager,
         caller.append("grompp");
         caller.addOption("-maxwarn", maxWarningsTolerated);
         runner->useTopGroAndNdxFromDatabase(simulationName);
-        auto firstPartMdpFieldValues = mdpFieldValues;
-        firstPartMdpFieldValues["nsteps"] = "8";
+        auto firstPartMdpFieldValues      = mdpFieldValues;
+        firstPartMdpFieldValues["nsteps"] = splitPoint;
         runner->useStringAsMdpFile(prepareMdpFileContents(firstPartMdpFieldValues));
         runner->tprFileName_ = firstPartRunTprFileName;
         EXPECT_EQ(0, runner->callGrompp(caller));
@@ -269,6 +275,9 @@ void runTest(TestFileManager            *fileManager,
         runner->edrFileName_ = fullRunEdrFileName;
         CommandLine fullRunCaller;
         fullRunCaller.append("mdrun");
+        /* Force neighborlist update at the beginning of the second half of the trajectory.
+         * Doing so through CLI options prevents pairlist tuning from changing it. */
+        fullRunCaller.addOption("-nstlist", splitPoint);
         ASSERT_EQ(0, runner->callMdrun(fullRunCaller));
     }
 
@@ -300,17 +309,17 @@ void runTest(TestFileManager            *fileManager,
 
     // Build the functor that will compare energy frames on the chosen
     // energy terms.
-    EnergyComparison energyComparison(energyTermsToCompare);
+    EnergyComparison energyComparison(energyTermsToCompare, MaxNumFrames::compareAllFrames());
 
     // Build the manager that will present matching pairs of frames to compare.
     //
     // TODO Here is an unnecessary copy of keys (ie. the energy term
     // names), for convenience. In the future, use a range.
     auto namesOfEnergiesToMatch = energyComparison.getEnergyNames();
-    ContinuationFramePairManager<EnergyFrameReader>
-         energyManager(openEnergyFileToReadTerms(fullRunEdrFileName, namesOfEnergiesToMatch),
-                  openEnergyFileToReadTerms(firstPartRunEdrFileName, namesOfEnergiesToMatch),
-                  openEnergyFileToReadTerms(secondPartRunEdrFileName, namesOfEnergiesToMatch));
+    ContinuationFramePairManager<EnergyFrameReader> energyManager(
+            openEnergyFileToReadTerms(fullRunEdrFileName, namesOfEnergiesToMatch),
+            openEnergyFileToReadTerms(firstPartRunEdrFileName, namesOfEnergiesToMatch),
+            openEnergyFileToReadTerms(secondPartRunEdrFileName, namesOfEnergiesToMatch));
     // Compare the energy frames.
     energyManager.compareAllFramePairs<EnergyFrame>(energyComparison);
 }
@@ -326,13 +335,13 @@ void runTest(TestFileManager            *fileManager,
  * without it?
  *
  * \todo Add FEP case. */
-class MdrunNoAppendContinuationIsExact : public MdrunTestFixture,
-                                         public ::testing::WithParamInterface <
-                                         std::tuple < std::string, std::string, std::string, std::string >>
+class MdrunNoAppendContinuationIsExact :
+    public MdrunTestFixture,
+    public ::testing::WithParamInterface<std::tuple<std::string, std::string, std::string, std::string, MdpParameterDatabase>>
 {
-    public:
-        //! Constructor
-        MdrunNoAppendContinuationIsExact() {}
+public:
+    //! Constructor
+    MdrunNoAppendContinuationIsExact() {}
 };
 
 /* Listing all of these is tedious, but there's no other way to get a
@@ -346,98 +355,182 @@ class MdrunNoAppendContinuationIsExact : public MdrunTestFixture,
 
 TEST_P(MdrunNoAppendContinuationIsExact, WithinTolerances)
 {
-    auto params              = GetParam();
-    auto simulationName      = std::get<0>(params);
-    auto integrator          = std::get<1>(params);
-    auto temperatureCoupling = std::get<2>(params);
-    auto pressureCoupling    = std::get<3>(params);
-    SCOPED_TRACE(formatString("Comparing normal and two-part run of simulation '%s' "
-                              "with integrator '%s'",
-                              simulationName.c_str(), integrator.c_str()));
+    auto params                  = GetParam();
+    auto simulationName          = std::get<0>(params);
+    auto integrator              = std::get<1>(params);
+    auto temperatureCoupling     = std::get<2>(params);
+    auto pressureCoupling        = std::get<3>(params);
+    auto additionalMdpParameters = std::get<4>(params);
+
+    // Check for unimplemented functionality
+    // TODO: Update this as modular simulator gains functionality
+    const bool isModularSimulatorExplicitlyDisabled = (getenv("GMX_DISABLE_MODULAR_SIMULATOR") != nullptr);
+    const bool isTCouplingCompatibleWithModularSimulator =
+            (temperatureCoupling == "no" || temperatureCoupling == "v-rescale"
+             || temperatureCoupling == "berendsen");
+    // GPU update is not compatible with modular simulator
+    const bool isGpuUpdateRequested = (getenv("GMX_FORCE_UPDATE_DEFAULT_GPU") != nullptr);
+    if (integrator == "md-vv" && pressureCoupling == "parrinello-rahman"
+        && (isModularSimulatorExplicitlyDisabled || !isTCouplingCompatibleWithModularSimulator
+            || isGpuUpdateRequested))
+    {
+        // Under md-vv, Parrinello-Rahman is only implemented for the modular simulator
+        return;
+    }
+    if (integrator == "md-vv" && temperatureCoupling == "nose-hoover"
+        && pressureCoupling == "berendsen")
+    {
+        // This combination is not implemented in either legacy or modular simulator
+        return;
+    }
+
+    SCOPED_TRACE(
+            formatString("Comparing normal and two-part run of simulation '%s' "
+                         "with integrator '%s'",
+                         simulationName.c_str(),
+                         integrator.c_str()));
 
     auto mdpFieldValues = prepareMdpFieldValues(simulationName.c_str(),
                                                 integrator.c_str(),
                                                 temperatureCoupling.c_str(),
-                                                pressureCoupling.c_str());
+                                                pressureCoupling.c_str(),
+                                                additionalMdpParameters);
     // The exact lambda state choice is unimportant, so long as there
     // is one when using an FEP input.
-    mdpFieldValues["other"] += formatString("\ninit-lambda-state = %d", 3);
+    mdpFieldValues["init-lambda-state"] = "3";
+    mdpFieldValues["nsteps"]            = "16";
 
-    // Forces on GPUs are generally not reproducible enough for a tight
-    // tolerance. Similarly, the propagation of sd and bd are not as
+    // Forces and update on GPUs are generally not reproducible enough for a tight
+    // tolerance. Similarly, the propagation of bd is not as
     // reproducible as the others. So we use several ULP tolerance
     // in all cases. This is looser than needed e.g. for md and md-vv
     // with forces on CPUs, but there is no real risk of a bug with
     // those propagators that would only be caught with a tighter
     // tolerance in this particular test.
-    EnergyTermsToCompare energyTermsToCompare
-    {{
-         {
-             interaction_function[F_EPOT].longname,
-             relativeToleranceAsPrecisionDependentUlp(10.0, 32, 64)
-         },
-     }};
+    int ulpToleranceInMixed  = 128;
+    int ulpToleranceInDouble = 64;
+    if (integrator == "bd")
+    {
+        // Somehow, the bd integrator has never been as reproducible
+        // as the others, either in continuations or reruns.
+        ulpToleranceInMixed = 200;
+    }
+    EnergyTermsToCompare energyTermsToCompare{
+        { { interaction_function[F_EPOT].longname,
+            relativeToleranceAsPrecisionDependentUlp(10.0, ulpToleranceInMixed, ulpToleranceInDouble) },
+          { interaction_function[F_EKIN].longname,
+            relativeToleranceAsPrecisionDependentUlp(10.0, ulpToleranceInMixed, ulpToleranceInDouble) } }
+    };
+
+    if (temperatureCoupling != "no" || pressureCoupling != "no")
+    {
+        if (simulationName == "alanine_vacuo")
+        {
+            // This is slightly less reproducible
+            energyTermsToCompare.insert({ interaction_function[F_ECONSERVED].longname,
+                                          relativeToleranceAsPrecisionDependentUlp(
+                                                  10.0, ulpToleranceInMixed * 2, ulpToleranceInDouble) });
+        }
+        else
+        {
+            energyTermsToCompare.insert({ interaction_function[F_ECONSERVED].longname,
+                                          relativeToleranceAsPrecisionDependentUlp(
+                                                  10.0, ulpToleranceInMixed, ulpToleranceInDouble) });
+        }
+    }
+
+    if (pressureCoupling == "parrinello-rahman")
+    {
+        energyTermsToCompare.insert({ "Box-Vel-XX",
+                                      relativeToleranceAsPrecisionDependentUlp(
+                                              1e-12, ulpToleranceInMixed, ulpToleranceInDouble) });
+        energyTermsToCompare.insert({ "Box-Vel-YY",
+                                      relativeToleranceAsPrecisionDependentUlp(
+                                              1e-12, ulpToleranceInMixed, ulpToleranceInDouble) });
+        energyTermsToCompare.insert({ "Box-Vel-ZZ",
+                                      relativeToleranceAsPrecisionDependentUlp(
+                                              1e-12, ulpToleranceInMixed, ulpToleranceInDouble) });
+    }
 
     int numWarningsToTolerate = 1;
-    runTest(&fileManager_, &runner_,
-            simulationName,
-            numWarningsToTolerate, mdpFieldValues,
-            energyTermsToCompare);
+    runTest(&fileManager_, &runner_, simulationName, numWarningsToTolerate, mdpFieldValues, energyTermsToCompare);
 }
 
 // TODO The time for OpenCL kernel compilation means these tests time
 // out. Once that compilation is cached for the whole process, these
 // tests can run in such configurations.
-#if GMX_GPU != GMX_GPU_OPENCL
-
-INSTANTIATE_TEST_CASE_P(NormalIntegrators, MdrunNoAppendContinuationIsExact,
-                            ::testing::Combine(::testing::Values("argon12", "spc2", "alanine_vsite_vacuo"),
-                                                   ::testing::Values("md", "md-vv", "bd", "sd"),
-                                                   ::testing::Values("no"),
-                                                   ::testing::Values("no")));
-
-INSTANTIATE_TEST_CASE_P(NormalIntegratorsWithFEP, MdrunNoAppendContinuationIsExact,
-                            ::testing::Combine(::testing::Values("nonanol_vacuo"),
-                                                   ::testing::Values("md", "md-vv", "bd", "sd"),
-                                                   ::testing::Values("no"),
-                                                   ::testing::Values("no")));
-
-INSTANTIATE_TEST_CASE_P(NormalNVT, MdrunNoAppendContinuationIsExact,
-                            ::testing::Combine(::testing::Values("argon12"),
-                                                   ::testing::Values("md", "md-vv"),
-                                                   ::testing::Values("berendsen", "v-rescale", "nose-hoover"),
-                                                   ::testing::Values("no")));
-
-INSTANTIATE_TEST_CASE_P(LeapfrogNPH, MdrunNoAppendContinuationIsExact,
-                            ::testing::Combine(::testing::Values("argon12"),
-                                                   ::testing::Values("md"),
-                                                   ::testing::Values("no"),
-                                                   ::testing::Values("berendsen", "parrinello-rahman")));
-
-INSTANTIATE_TEST_CASE_P(LeapfrogNPT, MdrunNoAppendContinuationIsExact,
-                            ::testing::Combine(::testing::Values("argon12"),
-                                                   ::testing::Values("md"),
-                                                   ::testing::Values("berendsen", "v-rescale", "nose-hoover"),
-                                                   ::testing::Values("berendsen", "parrinello-rahman")));
-
-INSTANTIATE_TEST_CASE_P(VelocityVerletNPH, MdrunNoAppendContinuationIsExact,
-                            ::testing::Combine(::testing::Values("argon12"),
-                                                   ::testing::Values("md-vv"),
-                                                   ::testing::Values("no"),
-                                                   ::testing::Values("berendsen")));
-
-INSTANTIATE_TEST_CASE_P(VelocityVerletNPT, MdrunNoAppendContinuationIsExact,
-                            ::testing::Combine(::testing::Values("argon12"),
-                                                   ::testing::Values("md-vv"),
-                                                   ::testing::Values("v-rescale"),
-                                                   ::testing::Values("berendsen")));
-
-INSTANTIATE_TEST_CASE_P(MTTK, MdrunNoAppendContinuationIsExact,
-                            ::testing::Combine(::testing::Values("argon12"),
-                                                   ::testing::Values("md-vv"),
-                                                   ::testing::Values("nose-hoover"),
-                                                   ::testing::Values("mttk")));
-
+#if !GMX_GPU_OPENCL
+
+INSTANTIATE_TEST_SUITE_P(
+        NormalIntegrators,
+        MdrunNoAppendContinuationIsExact,
+        ::testing::Combine(::testing::Values("argon12", "spc2", "alanine_vsite_vacuo"),
+                           ::testing::Values("md", "md-vv", "bd", "sd"),
+                           ::testing::Values("no"),
+                           ::testing::Values("no"),
+                           ::testing::Values(MdpParameterDatabase::Default)));
+
+INSTANTIATE_TEST_SUITE_P(NormalIntegratorsWithFEP,
+                         MdrunNoAppendContinuationIsExact,
+                         ::testing::Combine(::testing::Values("nonanol_vacuo"),
+                                            ::testing::Values("md", "md-vv", "bd", "sd"),
+                                            ::testing::Values("no"),
+                                            ::testing::Values("no"),
+                                            ::testing::Values(MdpParameterDatabase::Default)));
+
+INSTANTIATE_TEST_SUITE_P(
+        NVT,
+        MdrunNoAppendContinuationIsExact,
+        ::testing::Combine(::testing::Values("argon12"),
+                           ::testing::Values("md", "md-vv"),
+                           ::testing::Values("berendsen", "v-rescale", "nose-hoover"),
+                           ::testing::Values("no"),
+                           ::testing::Values(MdpParameterDatabase::Default)));
+
+INSTANTIATE_TEST_SUITE_P(
+        NPH,
+        MdrunNoAppendContinuationIsExact,
+        ::testing::Combine(::testing::Values("argon12"),
+                           ::testing::Values("md", "md-vv"),
+                           ::testing::Values("no"),
+                           ::testing::Values("berendsen", "parrinello-rahman", "C-rescale"),
+                           ::testing::Values(MdpParameterDatabase::Default)));
+
+INSTANTIATE_TEST_SUITE_P(
+        NPT,
+        MdrunNoAppendContinuationIsExact,
+        ::testing::Combine(::testing::Values("argon12"),
+                           ::testing::Values("md", "md-vv"),
+                           ::testing::Values("berendsen", "v-rescale", "nose-hoover"),
+                           ::testing::Values("berendsen", "parrinello-rahman", "C-rescale"),
+                           ::testing::Values(MdpParameterDatabase::Default)));
+
+INSTANTIATE_TEST_SUITE_P(MTTK,
+                         MdrunNoAppendContinuationIsExact,
+                         ::testing::Combine(::testing::Values("argon12"),
+                                            ::testing::Values("md-vv"),
+                                            ::testing::Values("nose-hoover"),
+                                            ::testing::Values("mttk"),
+                                            ::testing::Values(MdpParameterDatabase::Default)));
+
+INSTANTIATE_TEST_SUITE_P(Pull,
+                         MdrunNoAppendContinuationIsExact,
+                         ::testing::Combine(::testing::Values("spc2"),
+                                            ::testing::Values("md", "md-vv"),
+                                            ::testing::Values("no"),
+                                            ::testing::Values("no"),
+                                            ::testing::Values(MdpParameterDatabase::Pull)));
+
+INSTANTIATE_TEST_SUITE_P(Awh,
+                         MdrunNoAppendContinuationIsExact,
+                         ::testing::Combine(::testing::Values("alanine_vacuo"),
+                                            ::testing::Values("md", "md-vv"),
+                                            ::testing::Values("v-rescale"),
+                                            ::testing::Values("no"),
+                                            ::testing::Values(MdpParameterDatabase::Awh)));
+
+#else
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MdrunNoAppendContinuationIsExact);
 #endif
 
 } // namespace