Use new GPU infrastructure in MDLib tests
authorArtem Zhmurov <zhmurov@gmail.com>
Tue, 6 Oct 2020 09:37:49 +0000 (11:37 +0200)
committerArtem Zhmurov <zhmurov@gmail.com>
Tue, 6 Oct 2020 09:37:49 +0000 (11:37 +0200)
This make use of common device testing infrastructure in MDLib tests,
where both GPU and CPU implementations are tested. The GPU runners
will now be executed on all the detected devices, not only on the
default one. Also, this will allow to use the MDLib tests in OpenCL
and SYCL, where proper device context object is needed.

Closes #3317

Closes #2254

Related #2092

15 files changed:
src/gromacs/mdlib/tests/CMakeLists.txt
src/gromacs/mdlib/tests/constr.cpp
src/gromacs/mdlib/tests/constrtestrunners.cpp
src/gromacs/mdlib/tests/constrtestrunners.cu
src/gromacs/mdlib/tests/constrtestrunners.h
src/gromacs/mdlib/tests/leapfrog.cpp
src/gromacs/mdlib/tests/leapfrogtestrunners.cpp
src/gromacs/mdlib/tests/leapfrogtestrunners.cu
src/gromacs/mdlib/tests/leapfrogtestrunners.h
src/gromacs/mdlib/tests/settle.cpp
src/gromacs/mdlib/tests/settletestdata.cpp
src/gromacs/mdlib/tests/settletestdata.h
src/gromacs/mdlib/tests/settletestrunners.cpp
src/gromacs/mdlib/tests/settletestrunners.cu
src/gromacs/mdlib/tests/settletestrunners.h

index ab92f50785dd2ae6e03a2c38b97deaecb93ab1ab..465057ea528f6a85e810657b81084c9d4d8e8d85 100644 (file)
@@ -32,7 +32,7 @@
 # To help us fund GROMACS development, we humbly ask that you cite
 # the research papers on the package. Check out http://www.gromacs.org.
 
-gmx_add_unit_test(MdlibUnitTest mdlib-test
+gmx_add_unit_test(MdlibUnitTest mdlib-test HARDWARE_DETECTION
     CPP_SOURCE_FILES
         calc_verletbuf.cpp
         constr.cpp
index ade8dde632b53f47483c45c269c67dd079ab9576..75b691ef07813703da3cc1bdb0e9febe4a82a10c 100644 (file)
@@ -60,6 +60,7 @@
 #include "gromacs/pbcutil/pbc.h"
 #include "gromacs/utility/stringutil.h"
 
+#include "testutils/test_hardware_environment.h"
 #include "testutils/testasserts.h"
 
 #include "constrtestdata.h"
@@ -72,30 +73,6 @@ namespace test
 namespace
 {
 
-/*! \brief The two-dimensional parameter space for test.
- *
- * The test will run for all possible combinations of accessible
- * values of the:
- * 1. PBC setup ("PBCNONE" or "PBCXYZ")
- * 2. The algorithm ("SHAKE", "LINCS" or "LINCS_GPU").
- */
-typedef std::tuple<std::string, std::string> ConstraintsTestParameters;
-
-//! Names of all availible runners
-std::vector<std::string> runnersNames;
-
-//! Method that fills and returns runnersNames to the test macros.
-std::vector<std::string> getRunnersNames()
-{
-    runnersNames.emplace_back("SHAKE");
-    runnersNames.emplace_back("LINCS");
-    if (GMX_GPU_CUDA && canComputeOnDevice())
-    {
-        runnersNames.emplace_back("LINCS_GPU");
-    }
-    return runnersNames;
-}
-
 /*! \brief Test fixture for constraints.
  *
  * The fixture uses following test systems:
@@ -114,13 +91,11 @@ std::vector<std::string> getRunnersNames()
  * For some systems, the value for scaled virial tensor is checked against
  * pre-computed data.
  */
-class ConstraintsTest : public ::testing::TestWithParam<ConstraintsTestParameters>
+class ConstraintsTest : public ::testing::TestWithParam<std::string>
 {
 public:
     //! PBC setups
     std::unordered_map<std::string, t_pbc> pbcs_;
-    //! Algorithms (SHAKE and LINCS)
-    std::unordered_map<std::string, void (*)(ConstraintsTestData* testData, t_pbc pbc)> algorithms_;
 
     /*! \brief Test setup function.
      *
@@ -145,16 +120,6 @@ public:
         matrix boxXyz = { { 10.0, 0.0, 0.0 }, { 0.0, 20.0, 0.0 }, { 0.0, 0.0, 15.0 } };
         set_pbc(&pbc, PbcType::Xyz, boxXyz);
         pbcs_["PBCXYZ"] = pbc;
-
-        //
-        // Algorithms
-        //
-        // SHAKE
-        algorithms_["SHAKE"] = applyShake;
-        // LINCS
-        algorithms_["LINCS"] = applyLincs;
-        // LINCS using GPU (will only be called if GPU is available)
-        algorithms_["LINCS_GPU"] = applyLincsGpu;
     }
 
     /*! \brief
@@ -323,6 +288,23 @@ public:
             }
         }
     }
+    //! Before any test is run, work out whether any compatible GPUs exist.
+    static std::vector<std::unique_ptr<IConstraintsTestRunner>> getRunners()
+    {
+        std::vector<std::unique_ptr<IConstraintsTestRunner>> runners;
+        // Add runners for CPU versions of SHAKE and LINCS
+        runners.emplace_back(std::make_unique<ShakeConstraintsRunner>());
+        runners.emplace_back(std::make_unique<LincsConstraintsRunner>());
+        // If using CUDA, add runners for the GPU version of LINCS for each available GPU
+        if (GMX_GPU_CUDA)
+        {
+            for (const auto& testDevice : getTestHardwareEnvironment()->getTestDeviceList())
+            {
+                runners.emplace_back(std::make_unique<LincsDeviceConstraintsRunner>(*testDevice));
+            }
+        }
+        return runners;
+    }
 };
 
 TEST_P(ConstraintsTest, SingleConstraint)
@@ -355,20 +337,28 @@ TEST_P(ConstraintsTest, SingleConstraint)
             title, numAtoms, masses, constraints, constraintsR0, true, virialScaledRef, false, 0,
             real(0.0), real(0.001), x, xPrime, v, shakeTolerance, shakeUseSOR, lincsNIter,
             lincslincsExpansionOrder, lincsWarnAngle);
-    std::string pbcName;
-    std::string algorithmName;
-    std::tie(pbcName, algorithmName) = GetParam();
-    t_pbc pbc                        = pbcs_.at(pbcName);
 
-    // Apply constraints
-    algorithms_.at(algorithmName)(testData.get(), pbc);
+    std::string pbcName = GetParam();
+    t_pbc       pbc     = pbcs_.at(pbcName);
+
+    // Cycle through all available runners
+    for (const auto& runner : getRunners())
+    {
+        SCOPED_TRACE(formatString("Testing %s with %s using %s.", testData->title_.c_str(),
+                                  pbcName.c_str(), runner->name().c_str()));
+
+        testData->reset();
+
+        // Apply constraints
+        runner->applyConstraints(testData.get(), pbc);
 
-    checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
-    checkConstrainsDirection(*testData, pbc);
-    checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
-    checkCOMVelocity(absoluteTolerance(0.0001), *testData);
+        checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
+        checkConstrainsDirection(*testData, pbc);
+        checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
+        checkCOMVelocity(absoluteTolerance(0.0001), *testData);
 
-    checkVirialTensor(absoluteTolerance(0.0001), *testData);
+        checkVirialTensor(absoluteTolerance(0.0001), *testData);
+    }
 }
 
 TEST_P(ConstraintsTest, TwoDisjointConstraints)
@@ -407,20 +397,27 @@ TEST_P(ConstraintsTest, TwoDisjointConstraints)
             real(0.0), real(0.001), x, xPrime, v, shakeTolerance, shakeUseSOR, lincsNIter,
             lincslincsExpansionOrder, lincsWarnAngle);
 
-    std::string pbcName;
-    std::string algorithmName;
-    std::tie(pbcName, algorithmName) = GetParam();
-    t_pbc pbc                        = pbcs_.at(pbcName);
+    std::string pbcName = GetParam();
+    t_pbc       pbc     = pbcs_.at(pbcName);
+
+    // Cycle through all available runners
+    for (const auto& runner : getRunners())
+    {
+        SCOPED_TRACE(formatString("Testing %s with %s using %s.", testData->title_.c_str(),
+                                  pbcName.c_str(), runner->name().c_str()));
+
+        testData->reset();
 
-    // Apply constraints
-    algorithms_.at(algorithmName)(testData.get(), pbc);
+        // Apply constraints
+        runner->applyConstraints(testData.get(), pbc);
 
-    checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
-    checkConstrainsDirection(*testData, pbc);
-    checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
-    checkCOMVelocity(absoluteTolerance(0.0001), *testData);
+        checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
+        checkConstrainsDirection(*testData, pbc);
+        checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
+        checkCOMVelocity(absoluteTolerance(0.0001), *testData);
 
-    checkVirialTensor(absoluteTolerance(0.0001), *testData);
+        checkVirialTensor(absoluteTolerance(0.0001), *testData);
+    }
 }
 
 TEST_P(ConstraintsTest, ThreeSequentialConstraints)
@@ -459,20 +456,27 @@ TEST_P(ConstraintsTest, ThreeSequentialConstraints)
             real(0.0), real(0.001), x, xPrime, v, shakeTolerance, shakeUseSOR, lincsNIter,
             lincslincsExpansionOrder, lincsWarnAngle);
 
-    std::string pbcName;
-    std::string algorithmName;
-    std::tie(pbcName, algorithmName) = GetParam();
-    t_pbc pbc                        = pbcs_.at(pbcName);
+    std::string pbcName = GetParam();
+    t_pbc       pbc     = pbcs_.at(pbcName);
 
-    // Apply constraints
-    algorithms_.at(algorithmName)(testData.get(), pbc);
+    // Cycle through all available runners
+    for (const auto& runner : getRunners())
+    {
+        SCOPED_TRACE(formatString("Testing %s with %s using %s.", testData->title_.c_str(),
+                                  pbcName.c_str(), runner->name().c_str()));
 
-    checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
-    checkConstrainsDirection(*testData, pbc);
-    checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
-    checkCOMVelocity(absoluteTolerance(0.0001), *testData);
+        testData->reset();
 
-    checkVirialTensor(absoluteTolerance(0.0001), *testData);
+        // Apply constraints
+        runner->applyConstraints(testData.get(), pbc);
+
+        checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
+        checkConstrainsDirection(*testData, pbc);
+        checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
+        checkCOMVelocity(absoluteTolerance(0.0001), *testData);
+
+        checkVirialTensor(absoluteTolerance(0.0001), *testData);
+    }
 }
 
 TEST_P(ConstraintsTest, ThreeConstraintsWithCentralAtom)
@@ -512,20 +516,27 @@ TEST_P(ConstraintsTest, ThreeConstraintsWithCentralAtom)
             real(0.0), real(0.001), x, xPrime, v, shakeTolerance, shakeUseSOR, lincsNIter,
             lincslincsExpansionOrder, lincsWarnAngle);
 
-    std::string pbcName;
-    std::string algorithmName;
-    std::tie(pbcName, algorithmName) = GetParam();
-    t_pbc pbc                        = pbcs_.at(pbcName);
+    std::string pbcName = GetParam();
+    t_pbc       pbc     = pbcs_.at(pbcName);
+
+    // Cycle through all available runners
+    for (const auto& runner : getRunners())
+    {
+        SCOPED_TRACE(formatString("Testing %s with %s using %s.", testData->title_.c_str(),
+                                  pbcName.c_str(), runner->name().c_str()));
 
-    // Apply constraints
-    algorithms_.at(algorithmName)(testData.get(), pbc);
+        testData->reset();
 
-    checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
-    checkConstrainsDirection(*testData, pbc);
-    checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
-    checkCOMVelocity(absoluteTolerance(0.0001), *testData);
+        // Apply constraints
+        runner->applyConstraints(testData.get(), pbc);
 
-    checkVirialTensor(absoluteTolerance(0.0001), *testData);
+        checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
+        checkConstrainsDirection(*testData, pbc);
+        checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
+        checkCOMVelocity(absoluteTolerance(0.0001), *testData);
+
+        checkVirialTensor(absoluteTolerance(0.0001), *testData);
+    }
 }
 
 TEST_P(ConstraintsTest, FourSequentialConstraints)
@@ -564,20 +575,27 @@ TEST_P(ConstraintsTest, FourSequentialConstraints)
             real(0.0), real(0.001), x, xPrime, v, shakeTolerance, shakeUseSOR, lincsNIter,
             lincslincsExpansionOrder, lincsWarnAngle);
 
-    std::string pbcName;
-    std::string algorithmName;
-    std::tie(pbcName, algorithmName) = GetParam();
-    t_pbc pbc                        = pbcs_.at(pbcName);
+    std::string pbcName = GetParam();
+    t_pbc       pbc     = pbcs_.at(pbcName);
+
+    // Cycle through all available runners
+    for (const auto& runner : getRunners())
+    {
+        SCOPED_TRACE(formatString("Testing %s with %s using %s.", testData->title_.c_str(),
+                                  pbcName.c_str(), runner->name().c_str()));
+
+        testData->reset();
 
-    // Apply constraints
-    algorithms_.at(algorithmName)(testData.get(), pbc);
+        // Apply constraints
+        runner->applyConstraints(testData.get(), pbc);
 
-    checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
-    checkConstrainsDirection(*testData, pbc);
-    checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
-    checkCOMVelocity(absoluteTolerance(0.0001), *testData);
+        checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
+        checkConstrainsDirection(*testData, pbc);
+        checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
+        checkCOMVelocity(absoluteTolerance(0.0001), *testData);
 
-    checkVirialTensor(absoluteTolerance(0.01), *testData);
+        checkVirialTensor(absoluteTolerance(0.01), *testData);
+    }
 }
 
 TEST_P(ConstraintsTest, TriangleOfConstraints)
@@ -615,27 +633,31 @@ TEST_P(ConstraintsTest, TriangleOfConstraints)
             real(0.0), real(0.001), x, xPrime, v, shakeTolerance, shakeUseSOR, lincsNIter,
             lincslincsExpansionOrder, lincsWarnAngle);
 
-    std::string pbcName;
-    std::string runnerName;
-    std::tie(pbcName, runnerName) = GetParam();
-    t_pbc pbc                     = pbcs_.at(pbcName);
+    std::string pbcName = GetParam();
+    t_pbc       pbc     = pbcs_.at(pbcName);
+
+    // Cycle through all available runners
+    for (const auto& runner : getRunners())
+    {
+        SCOPED_TRACE(formatString("Testing %s with %s using %s.", testData->title_.c_str(),
+                                  pbcName.c_str(), runner->name().c_str()));
 
-    // Apply constraints
-    algorithms_.at(runnerName)(testData.get(), pbc);
+        testData->reset();
 
-    checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
-    checkConstrainsDirection(*testData, pbc);
-    checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
-    checkCOMVelocity(absoluteTolerance(0.0001), *testData);
+        // Apply constraints
+        runner->applyConstraints(testData.get(), pbc);
 
-    checkVirialTensor(absoluteTolerance(0.00001), *testData);
+        checkConstrainsLength(absoluteTolerance(0.0002), *testData, pbc);
+        checkConstrainsDirection(*testData, pbc);
+        checkCOMCoordinates(absoluteTolerance(0.0001), *testData);
+        checkCOMVelocity(absoluteTolerance(0.0001), *testData);
+
+        checkVirialTensor(absoluteTolerance(0.00001), *testData);
+    }
 }
 
 
-INSTANTIATE_TEST_CASE_P(WithParameters,
-                        ConstraintsTest,
-                        ::testing::Combine(::testing::Values("PBCNone", "PBCXYZ"),
-                                           ::testing::ValuesIn(getRunnersNames())));
+INSTANTIATE_TEST_CASE_P(WithParameters, ConstraintsTest, ::testing::Values("PBCNone", "PBCXYZ"));
 
 } // namespace
 } // namespace test
index 5fca4c06b31b94774d0d6b560b35d1467a1f7bc6..f57a9de20dc169a23e6d1cb99d5aed74aa61c269 100644 (file)
@@ -80,13 +80,7 @@ namespace gmx
 namespace test
 {
 
-/*! \brief
- * Initialize and apply SHAKE constraints.
- *
- * \param[in] testData        Test data structure.
- * \param[in] pbc             Periodic boundary data.
- */
-void applyShake(ConstraintsTestData* testData, t_pbc gmx_unused pbc)
+void ShakeConstraintsRunner::applyConstraints(ConstraintsTestData* testData, t_pbc /* pbc */)
 {
     shakedata shaked;
     make_shake_sblock_serial(&shaked, testData->idef_.get(), testData->numAtoms_);
@@ -98,13 +92,7 @@ void applyShake(ConstraintsTestData* testData, t_pbc gmx_unused pbc)
     EXPECT_TRUE(success) << "Test failed with a false return value in SHAKE.";
 }
 
-/*! \brief
- * Initialize and apply LINCS constraints.
- *
- * \param[in] testData        Test data structure.
- * \param[in] pbc             Periodic boundary data.
- */
-void applyLincs(ConstraintsTestData* testData, t_pbc pbc)
+void LincsConstraintsRunner::applyConstraints(ConstraintsTestData* testData, t_pbc pbc)
 {
 
     Lincs* lincsd;
@@ -150,14 +138,9 @@ void applyLincs(ConstraintsTestData* testData, t_pbc pbc)
 }
 
 #if !GMX_GPU_CUDA
-/*! \brief
- * Stub for GPU version of LINCS constraints to satisfy compiler.
- *
- * \param[in] testData        Test data structure.
- * \param[in] pbc             Periodic boundary data.
- */
-void applyLincsGpu(ConstraintsTestData gmx_unused* testData, t_pbc gmx_unused pbc)
+void LincsDeviceConstraintsRunner::applyConstraints(ConstraintsTestData* /* testData */, t_pbc /* pbc */)
 {
+    GMX_UNUSED_VALUE(testDevice_);
     FAIL() << "Dummy LINCS CUDA function was called instead of the real one.";
 }
 #endif // !GMX_GPU_CUDA
index 62b713cd7b76cf8e34f16aa379e78921677311af..88cc8e866d48b951af5c795c7e3140e388b3554d 100644 (file)
@@ -62,17 +62,11 @@ namespace gmx
 namespace test
 {
 
-/*! \brief
- * Initialize and apply LINCS constraints on GPU.
- *
- * \param[in] testData        Test data structure.
- * \param[in] pbc             Periodic boundary data.
- */
-void applyLincsGpu(ConstraintsTestData* testData, t_pbc pbc)
+void LincsDeviceConstraintsRunner::applyConstraints(ConstraintsTestData* testData, t_pbc pbc)
 {
-    DeviceInformation   deviceInfo;
-    const DeviceContext deviceContext(deviceInfo);
-    const DeviceStream  deviceStream(deviceContext, DeviceStreamPriority::Normal, false);
+    const DeviceContext& deviceContext = testDevice_.deviceContext();
+    const DeviceStream&  deviceStream  = testDevice_.deviceStream();
+    setActiveDevice(testDevice_.deviceInfo());
 
     auto lincsGpu = std::make_unique<LincsGpu>(testData->ir_.nLincsIter, testData->ir_.nProjOrder,
                                                deviceContext, deviceStream);
index a9e715625649d9c6d537ec24bdc331691087db63..d479e5537cd4f7875e3514d26e562aa0cd2152d5 100644 (file)
  * the research papers on the package. Check out http://www.gromacs.org.
  */
 /*! \internal \file
- * \brief SHAKE and LINCS tests header.
+ * \brief SHAKE and LINCS tests runners.
  *
- * Contains description and constructor for the test data accumulating object,
- * declares CPU- and GPU-based functions used to apply SHAKE or LINCS on the
- * test data.
+ * Declares test runner class for constraints. The test runner abstract class is used
+ * to unify the interfaces for different constraints methods, running on different
+ * hardware.  This allows to run the same test on the same data using different
+ * implementations of the parent class, that inherit its interfaces.
  *
  * \author Artem Zhmurov <zhmurov@gmail.com>
  * \ingroup module_mdlib
 #ifndef GMX_MDLIB_TESTS_CONSTRTESTRUNNERS_H
 #define GMX_MDLIB_TESTS_CONSTRTESTRUNNERS_H
 
+#include <gtest/gtest.h>
+
+#include "testutils/test_device.h"
+
 #include "constrtestdata.h"
 
 struct t_pbc;
@@ -55,18 +60,92 @@ namespace gmx
 namespace test
 {
 
-/*! \brief Apply SHAKE constraints to the test data.
- */
-void applyShake(ConstraintsTestData* testData, t_pbc pbc);
-/*! \brief Apply LINCS constraints to the test data.
- */
-void applyLincs(ConstraintsTestData* testData, t_pbc pbc);
-/*! \brief Apply GPU version of LINCS constraints to the test data.
+/* \brief Constraints test runner interface.
  *
- * All the data is copied to the GPU device, then LINCS is applied and
- * the resulting coordinates are copied back.
+ * Wraps the actual implementation of constraints algorithm into common interface.
  */
-void applyLincsGpu(ConstraintsTestData* testData, t_pbc pbc);
+class IConstraintsTestRunner
+{
+public:
+    //! Virtual destructor.
+    virtual ~IConstraintsTestRunner() {}
+    /*! \brief Abstract constraining function. Should be overriden.
+     *
+     * \param[in] testData             Test data structure.
+     * \param[in] pbc                  Periodic boundary data.
+     */
+    virtual void applyConstraints(ConstraintsTestData* testData, t_pbc pbc) = 0;
+
+    /*! \brief Get the name of the implementation.
+     *
+     * \return "<algorithm> on <device>", depending on the actual implementation used. E.g., "LINCS on #0: NVIDIA GeForce GTX 1660 SUPER".
+     */
+    virtual std::string name() = 0;
+};
+
+// Runner for the CPU implementation of SHAKE constraints algorithm.
+class ShakeConstraintsRunner : public IConstraintsTestRunner
+{
+public:
+    //! Default constructor.
+    ShakeConstraintsRunner() {}
+    /*! \brief Apply SHAKE constraints to the test data.
+     *
+     * \param[in] testData             Test data structure.
+     * \param[in] pbc                  Periodic boundary data.
+     */
+    void applyConstraints(ConstraintsTestData* testData, t_pbc pbc) override;
+    /*! \brief Get the name of the implementation.
+     *
+     * \return "SHAKE" string;
+     */
+    std::string name() override { return "SHAKE on CPU"; }
+};
+
+// Runner for the CPU implementation of LINCS constraints algorithm.
+class LincsConstraintsRunner : public IConstraintsTestRunner
+{
+public:
+    //! Default constructor.
+    LincsConstraintsRunner() {}
+    /*! \brief Apply LINCS constraints to the test data on the CPU.
+     *
+     * \param[in] testData             Test data structure.
+     * \param[in] pbc                  Periodic boundary data.
+     */
+    void applyConstraints(ConstraintsTestData* testData, t_pbc pbc) override;
+    /*! \brief Get the name of the implementation.
+     *
+     * \return "LINCS" string;
+     */
+    std::string name() override { return "LINCS on CPU"; }
+};
+
+// Runner for the GPU implementation of LINCS constraints algorithm.
+class LincsDeviceConstraintsRunner : public IConstraintsTestRunner
+{
+public:
+    /*! \brief Constructor. Keeps a copy of the hardware context.
+     *
+     * \param[in] testDevice The device hardware context to be used by the runner.
+     */
+    LincsDeviceConstraintsRunner(const TestDevice& testDevice) : testDevice_(testDevice) {}
+    /*! \brief Apply LINCS constraints to the test data on the GPU.
+     *
+     * \param[in] testData             Test data structure.
+     * \param[in] pbc                  Periodic boundary data.
+     */
+    void applyConstraints(ConstraintsTestData* testData, t_pbc pbc) override;
+    /*! \brief Get the name of the implementation.
+     *
+     * \return "LINCS_GPU" string;
+     */
+    std::string name() override { return "LINCS on " + testDevice_.description(); }
+
+private:
+    //! Test device to be used in the runner.
+    const TestDevice& testDevice_;
+};
 
 } // namespace test
 } // namespace gmx
index 3018d295cac3e914157e39480c6f5000de052cc0..79c257abb3bd0b588ca619ad2a160a15eb3b3802 100644 (file)
@@ -70,6 +70,7 @@
 #include "gromacs/utility/stringutil.h"
 
 #include "testutils/refdata.h"
+#include "testutils/test_hardware_environment.h"
 #include "testutils/testasserts.h"
 
 #include "leapfrogtestdata.h"
@@ -137,8 +138,6 @@ const LeapFrogTestParameters parametersSets[] = {
 class LeapFrogTest : public ::testing::TestWithParam<LeapFrogTestParameters>
 {
 public:
-    //! Availiable runners (CPU and GPU versions of the Leap-Frog)
-    static std::unordered_map<std::string, void (*)(LeapFrogTestData* testData, const int numSteps)> s_runners_;
     //! Reference data
     TestReferenceData refData_;
     //! Checker for reference data
@@ -146,19 +145,6 @@ public:
 
     LeapFrogTest() : checker_(refData_.rootChecker()) {}
 
-    //! Setup the runners one for all parameters sets
-    static void SetUpTestCase()
-    {
-        //
-        // All runners should be registered here under appropriate conditions
-        //
-        s_runners_["LeapFrogSimple"] = integrateLeapFrogSimple;
-        if (GMX_GPU_CUDA && canComputeOnDevice())
-        {
-            s_runners_["LeapFrogGpu"] = integrateLeapFrogGpu;
-        }
-    }
-
     /*! \brief Test the numerical integrator against analytical solution for simple constant force case.
      *
      * \param[in]  tolerance  Tolerance
@@ -223,21 +209,31 @@ public:
     }
 };
 
-std::unordered_map<std::string, void (*)(LeapFrogTestData* testData, const int numSteps)> LeapFrogTest::s_runners_;
-
 TEST_P(LeapFrogTest, SimpleIntegration)
 {
-    // Cycle through all available runners
-    for (const auto& runner : s_runners_)
+    // Construct the list of runners
+    std::vector<std::unique_ptr<ILeapFrogTestRunner>> runners;
+    // Add runners for CPU version
+    runners.emplace_back(std::make_unique<LeapFrogHostTestRunner>());
+    // If using CUDA, add runners for the GPU version for each available GPU
+    if (GMX_GPU_CUDA)
     {
-        std::string runnerName = runner.first;
+        for (const auto& testDevice : getTestHardwareEnvironment()->getTestDeviceList())
+        {
+            runners.emplace_back(std::make_unique<LeapFrogDeviceTestRunner>(*testDevice));
+        }
+    }
 
+    for (const auto& runner : runners)
+    {
         LeapFrogTestParameters parameters = GetParam();
 
         std::string testDescription = formatString(
-                "Testing %s with %d atoms for %d timesteps with %d temperature coupling groups and "
-                "%s pressure coupling (dt = %f, v0=(%f, %f, %f), f0=(%f, %f, %f), nstpcouple = %d)",
-                runnerName.c_str(), parameters.numAtoms, parameters.numSteps,
+                "Testing on %s with %d atoms for %d timesteps with %d temperature coupling "
+                "groups and "
+                "%s pressure coupling (dt = %f, v0=(%f, %f, %f), f0=(%f, %f, %f), nstpcouple = "
+                "%d)",
+                runner->hardwareDescription().c_str(), parameters.numAtoms, parameters.numSteps,
                 parameters.numTCoupleGroups, parameters.nstpcouple == 0 ? "without" : "with",
                 parameters.timestep, parameters.v[XX], parameters.v[YY], parameters.v[ZZ],
                 parameters.f[XX], parameters.f[YY], parameters.f[ZZ], parameters.nstpcouple);
@@ -247,7 +243,7 @@ TEST_P(LeapFrogTest, SimpleIntegration)
                 parameters.numAtoms, parameters.timestep, parameters.v, parameters.f,
                 parameters.numTCoupleGroups, parameters.nstpcouple);
 
-        runner.second(testData.get(), parameters.numSteps);
+        runner->integrate(testData.get(), parameters.numSteps);
 
         real totalTime = parameters.numSteps * parameters.timestep;
         // TODO For the case of constant force, the numerical scheme is exact and
index 492fb8e080065729c6896cca710802c5b2e5fb83..2978665cb49f599bb90c97fdbe0cd50e51120a12 100644 (file)
@@ -70,7 +70,7 @@ namespace gmx
 namespace test
 {
 
-void integrateLeapFrogSimple(LeapFrogTestData* testData, int numSteps)
+void LeapFrogHostTestRunner::integrate(LeapFrogTestData* testData, int numSteps)
 {
     testData->state_.x.resizeWithPadding(testData->numAtoms_);
     testData->state_.v.resizeWithPadding(testData->numAtoms_);
@@ -105,8 +105,9 @@ void integrateLeapFrogSimple(LeapFrogTestData* testData, int numSteps)
 
 #if !GMX_GPU_CUDA
 
-void integrateLeapFrogGpu(gmx_unused LeapFrogTestData* testData, gmx_unused int numSteps)
+void LeapFrogDeviceTestRunner::integrate(LeapFrogTestData* /* testData */, int /* numSteps */)
 {
+    GMX_UNUSED_VALUE(testDevice_);
     FAIL() << "Dummy Leap-Frog CUDA function was called instead of the real one.";
 }
 
index 7f9a5766a375cf65d9d8fb58b5763e50a0fa6897..7089794f5ae2596f8f821633e5cd5f4ad4cd4280 100644 (file)
@@ -64,11 +64,11 @@ namespace gmx
 namespace test
 {
 
-void integrateLeapFrogGpu(LeapFrogTestData* testData, int numSteps)
+void LeapFrogDeviceTestRunner::integrate(LeapFrogTestData* testData, int numSteps)
 {
-    DeviceInformation   deviceInfo;
-    const DeviceContext deviceContext(deviceInfo);
-    const DeviceStream  deviceStream(deviceContext, DeviceStreamPriority::Normal, false);
+    const DeviceContext& deviceContext = testDevice_.deviceContext();
+    const DeviceStream&  deviceStream  = testDevice_.deviceStream();
+    setActiveDevice(testDevice_.deviceInfo());
 
     int numAtoms = testData->numAtoms_;
 
index bef6c49d1ab65f31c7beed5029db0992b4aee2df..deac1d0a68bae0404db3c74b201cb8f6b6005264 100644 (file)
  * the research papers on the package. Check out http://www.gromacs.org.
  */
 /*! \internal \file
- * \brief Declaration of interfaces to run various implementations of integrator (runners)
+ * \brief Leap-Frog tests runners.
+ *
+ * Declares test runner class for Leap-Frog algorithm. The test runners abstract
+ * class is used to unify the interfaces for CPU and GPU implementations of the
+ * Leap-Frog algorithm. This allows to run the same test on the same data using
+ * different implementations of the parent class, that inherit its interfaces.
  *
  * \author Artem Zhmurov <zhmurov@gmail.com>
  * \ingroup module_mdlib
  */
-
 #ifndef GMX_MDLIB_TESTS_LEAPFROGTESTRUNNERS_H
 #define GMX_MDLIB_TESTS_LEAPFROGTESTRUNNERS_H
 
 #include "gromacs/math/vec.h"
 
+#include "testutils/test_device.h"
+
 #include "leapfrogtestdata.h"
 
 namespace gmx
@@ -51,23 +57,81 @@ namespace gmx
 namespace test
 {
 
-/*! \brief Integrate using CPU version of Leap-Frog
+/* \brief LeapFrog integrator test runner interface.
  *
- * \param[in]     testData  Data needed for the integrator
- * \param[in]     numSteps  Total number of steps to run integration for.
+ * Wraps the actual implementation of LeapFrog algorithm into common interface.
  */
-void integrateLeapFrogSimple(LeapFrogTestData* testData, int numSteps);
+class ILeapFrogTestRunner
+{
+public:
+    //! Virtual destructor
+    virtual ~ILeapFrogTestRunner() {}
+    /*! \brief The abstract function that runs the integrator for a given number of steps.
+     *
+     * Should be overriden.
+     *
+     * \param[in]     testData  Data needed for the integrator
+     * \param[in]     numSteps  Total number of steps to run integration for.
+     */
+    virtual void integrate(LeapFrogTestData* testData, int numSteps) = 0;
 
-/*! \brief Integrate using GPU version of Leap-Frog
- *
- * Copies data from CPU to GPU, integrates the equation of motion
- * for requested number of steps using Leap-Frog algorithm, copies
- * the result back.
- *
- * \param[in]     testData  Data needed for the integrator
- * \param[in]     numSteps  Total number of steps to run integration for.
- */
-void integrateLeapFrogGpu(LeapFrogTestData* testData, int numSteps);
+    /*! \brief Get the human-friendly description of hardware used by the runner.
+     *
+     * \returns String with description of the hardware.
+     */
+    virtual std::string hardwareDescription() = 0;
+};
+
+// Runner for the CPU version of Leap-Frog.
+class LeapFrogHostTestRunner : public ILeapFrogTestRunner
+{
+public:
+    //! Constructor.
+    LeapFrogHostTestRunner() {}
+    /*! \brief Integrate on the CPU for a given number of steps.
+     *
+     * Will update the test data with the integration result.
+     *
+     * \param[in]     testData  Data needed for the integrator
+     * \param[in]     numSteps  Total number of steps to run integration for.
+     */
+    void integrate(LeapFrogTestData* testData, int numSteps) override;
+    /*! \brief Get the hardware description
+     *
+     * \returns "CPU" string.
+     */
+    std::string hardwareDescription() override { return "CPU"; }
+};
+
+// Runner for the CPU version of Leap-Frog.
+class LeapFrogDeviceTestRunner : public ILeapFrogTestRunner
+{
+public:
+    /*! \brief Constructor. Keeps a copy of the hardware context.
+     *
+     * \param[in] testDevice The device hardware context to be used by the runner.
+     */
+    LeapFrogDeviceTestRunner(const TestDevice& testDevice) : testDevice_(testDevice) {}
+    /*! \brief Integrate on the GPU for a given number of steps.
+     *
+     * Copies data from CPU to GPU, integrates the equation of motion
+     * for requested number of steps using Leap-Frog algorithm, copies
+     * the result back.
+     *
+     * \param[in]     testData  Data needed for the integrator
+     * \param[in]     numSteps  Total number of steps to run integration for.
+     */
+    void integrate(LeapFrogTestData* testData, int numSteps) override;
+    /*! \brief Get the hardware description
+     *
+     * \returns A string with GPU description.
+     */
+    std::string hardwareDescription() override { return testDevice_.description(); }
+
+private:
+    //! Test device to be used in the runner.
+    const TestDevice& testDevice_;
+};
 
 } // namespace test
 } // namespace gmx
index 9dc2d9d505f40b7176602c440f09cac17975d352..e565b52848700f347d30fa0dbaf5213a092be50f 100644 (file)
@@ -93,6 +93,7 @@
 
 #include "gromacs/mdlib/tests/watersystem.h"
 #include "testutils/refdata.h"
+#include "testutils/test_hardware_environment.h"
 #include "testutils/testasserts.h"
 
 #include "settletestdata.h"
@@ -144,10 +145,6 @@ class SettleTest : public ::testing::TestWithParam<SettleTestParameters>
 public:
     //! PBC setups
     std::unordered_map<std::string, t_pbc> pbcs_;
-    //! Runners (CPU and GPU versions of SETTLE)
-    std::unordered_map<std::string,
-                       void (*)(SettleTestData* testData, const t_pbc pbc, const bool updateVelocities, const bool calcVirial, const std::string& testDescription)>
-            runners_;
     //! Reference data
     TestReferenceData refData_;
     //! Checker for reference data
@@ -176,21 +173,6 @@ public:
         matrix boxXyz = { { real(1.86206), 0, 0 }, { 0, real(1.86206), 0 }, { 0, 0, real(1.86206) } };
         set_pbc(&pbc, PbcType::Xyz, boxXyz);
         pbcs_["PBCXYZ"] = pbc;
-
-        //
-        // All SETTLE runners should be registered here under appropriate conditions
-        //
-        runners_["SETTLE"] = applySettle;
-
-        // CUDA version will be tested only if:
-        // 1. The code was compiled with CUDA
-        // 2. There is a CUDA-capable GPU in a system
-        // 3. This GPU is detectable
-        // 4. GPU detection was not disabled by GMX_DISABLE_GPU_DETECTION environment variable
-        if (GMX_GPU_CUDA && s_hasCompatibleGpus)
-        {
-            runners_["SETTLE_GPU"] = applySettleGpu;
-        }
     }
 
     /*! \brief Check if the final interatomic distances are equal to target set by constraints.
@@ -322,22 +304,24 @@ public:
         virialRef.checkReal(virial[ZZ][YY], "ZY");
         virialRef.checkReal(virial[ZZ][ZZ], "ZZ");
     }
-
-    //! Store whether any compatible GPUs exist.
-    static bool s_hasCompatibleGpus;
-    //! Before any test is run, work out whether any compatible GPUs exist.
-    static void SetUpTestCase() { s_hasCompatibleGpus = canComputeOnDevice(); }
 };
 
-bool SettleTest::s_hasCompatibleGpus = false;
-
 TEST_P(SettleTest, SatisfiesConstraints)
 {
-    // Cycle through all available runners
-    for (const auto& runner : runners_)
+    // Construct the list of runners
+    std::vector<std::unique_ptr<ISettleTestRunner>> runners;
+    // Add runners for CPU version
+    runners.emplace_back(std::make_unique<SettleHostTestRunner>());
+    // If using CUDA, add runners for the GPU version for each available GPU
+    if (GMX_GPU_CUDA)
+    {
+        for (const auto& testDevice : getTestHardwareEnvironment()->getTestDeviceList())
+        {
+            runners.emplace_back(std::make_unique<SettleDeviceTestRunner>(*testDevice));
+        }
+    }
+    for (const auto& runner : runners)
     {
-        std::string runnerName = runner.first;
-
         // Make some symbolic names for the parameter combination.
         SettleTestParameters params = GetParam();
 
@@ -351,7 +335,7 @@ TEST_P(SettleTest, SatisfiesConstraints)
         // being tested, to help make failing tests comprehensible.
         std::string testDescription = formatString(
                 "Testing %s with %d SETTLEs, %s, %svelocities and %scalculating the virial.",
-                runnerName.c_str(), numSettles, pbcName.c_str(),
+                runner->hardwareDescription().c_str(), numSettles, pbcName.c_str(),
                 updateVelocities ? "with " : "without ", calcVirial ? "" : "not ");
 
         SCOPED_TRACE(testDescription);
@@ -364,7 +348,7 @@ TEST_P(SettleTest, SatisfiesConstraints)
         t_pbc pbc = pbcs_.at(pbcName);
 
         // Apply SETTLE
-        runner.second(testData.get(), pbc, updateVelocities, calcVirial, testDescription);
+        runner->applySettle(testData.get(), pbc, updateVelocities, calcVirial, testDescription);
 
         // The necessary tolerances for the test to pass were determined
         // empirically. This isn't nice, but the required behavior that
index af9c9cc7ee6e4c33d4255a602f341b537c12819a..158db19fe7442c0323d86e421fc0767405d20f03 100644 (file)
@@ -73,6 +73,7 @@ namespace test
 {
 
 SettleTestData::SettleTestData(int numSettles) :
+    numSettles_(numSettles),
     x_(c_waterPositions.size()),
     xPrime_(c_waterPositions.size()),
     v_(c_waterPositions.size())
index fc115f1a228df1c384d579ffc417684dd5955aeb..45d1c946b898d21f8a1834e60df71bed5906c37a 100644 (file)
@@ -62,6 +62,8 @@ namespace test
 class SettleTestData
 {
 public:
+    //! Number of settles
+    int numSettles_;
     //! Initial (undisturbed) positions
     PaddedVector<gmx::RVec> x_;
     //! Updated water atom positions to constrain
index 3fd90dc6892272e3f181bc5b949a694f2ef0df41..cfc2b0ca20ae3142340d0227573141337487ebb7 100644 (file)
@@ -57,11 +57,11 @@ namespace gmx
 namespace test
 {
 
-void applySettle(SettleTestData*    testData,
-                 const t_pbc        pbc,
-                 const bool         updateVelocities,
-                 const bool         calcVirial,
-                 const std::string& testDescription)
+void SettleHostTestRunner::applySettle(SettleTestData*    testData,
+                                       const t_pbc        pbc,
+                                       const bool         updateVelocities,
+                                       const bool         calcVirial,
+                                       const std::string& testDescription)
 {
     SettleData settled(testData->mtop_);
 
@@ -80,12 +80,13 @@ void applySettle(SettleTestData*    testData,
 
 #if !GMX_GPU_CUDA
 
-void applySettleGpu(gmx_unused SettleTestData* testData,
-                    gmx_unused const t_pbc pbc,
-                    gmx_unused const bool  updateVelocities,
-                    gmx_unused const bool  calcVirial,
-                    gmx_unused const std::string& testDescription)
+void SettleDeviceTestRunner::applySettle(SettleTestData* /* testData */,
+                                         const t_pbc /* pbc */,
+                                         const bool /* updateVelocities */,
+                                         const bool /* calcVirial */,
+                                         const std::string& /* testDescription */)
 {
+    GMX_UNUSED_VALUE(testDevice_);
     FAIL() << "Dummy SETTLE GPU function was called instead of the real one in the SETTLE test.";
 }
 
index 930f4cb5ba5c18fb10701f10dde565bc3a8811ca..1200626ad07dc69cfe97eb3cc8a60b9e65f5048f 100644 (file)
 #include "gromacs/mdlib/settle_gpu.cuh"
 #include "gromacs/utility/unique_cptr.h"
 
+#include "testutils/test_device.h"
+
 namespace gmx
 {
 namespace test
 {
 
-/*! \brief Apply SETTLE using GPU version of the algorithm.
- *
- * Initializes SETTLE object, copied data to the GPU, applies algorithm, copies the data back,
- * destroys the object. The coordinates, velocities and virial are updated in the testData object.
- *
- * \param[in,out] testData          An object, containing all the data structures needed by SETTLE.
- * \param[in]     pbc               Periodic boundary setup.
- * \param[in]     updateVelocities  If the velocities should be updated.
- * \param[in]     calcVirial        If the virial should be computed.
- * \param[in]     testDescription   Brief description that will be printed in case of test failure.
- */
-void applySettleGpu(SettleTestData*  testData,
-                    const t_pbc      pbc,
-                    const bool       updateVelocities,
-                    const bool       calcVirial,
-                    gmx_unused const std::string& testDescription)
+void SettleDeviceTestRunner::applySettle(SettleTestData* testData,
+                                         const t_pbc     pbc,
+                                         const bool      updateVelocities,
+                                         const bool      calcVirial,
+                                         const std::string& /* testDescription */)
 {
     // These should never fail since this function should only be called if CUDA is enabled and
     // there is a CUDA-capable device available.
     GMX_RELEASE_ASSERT(GMX_GPU_CUDA, "CUDA version of SETTLE was called from non-CUDA build.");
 
-    DeviceInformation   deviceInfo;
-    const DeviceContext deviceContext(deviceInfo);
-    const DeviceStream  deviceStream(deviceContext, DeviceStreamPriority::Normal, false);
+    const DeviceContext& deviceContext = testDevice_.deviceContext();
+    const DeviceStream&  deviceStream  = testDevice_.deviceStream();
+    setActiveDevice(testDevice_.deviceInfo());
 
     auto settleGpu = std::make_unique<SettleGpu>(testData->mtop_, deviceContext, deviceStream);
 
index 08761213755ed4ace4d612a43d4213bb7d5c2eba..3f643f605e30b7c7c4ea62a74212e9ff2e857613 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, 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.
  * the research papers on the package. Check out http://www.gromacs.org.
  */
 /*! \internal \file
- * \brief Declaration of the SETTLE tests runners.
+ * \brief SETTLE tests runners.
  *
- * Declares the functions that do the buffer management and apply
- * SETTLE constraints ("test runners").
+ * Declares test runner class for SETTLE algorithm. The test runners abstract
+ * class is used to unify the interfaces for CPU and GPU implementations of the
+ * SETTLE algorithm. This allows to run the same test on the same data using
+ * different implementations of the parent class, that inherit its interfaces.
  *
  * \author Artem Zhmurov <zhmurov@gmail.com>
  * \ingroup module_mdlib
  */
-
 #ifndef GMX_MDLIB_TESTS_SETTLETESTRUNNERS_H
 #define GMX_MDLIB_TESTS_SETTLETESTRUNNERS_H
 
+#include <gtest/gtest.h>
+
+#include "testutils/test_device.h"
+
 #include "settletestdata.h"
 
 struct t_pbc;
@@ -54,39 +59,103 @@ namespace gmx
 namespace test
 {
 
-/*! \brief Apply SETTLE using CPU version of the algorithm
- *
- * Initializes SETTLE object, applies algorithm, destroys the object. The coordinates, velocities
- * and virial are updated in the testData object.
+/* \brief SETTLE test runner interface.
  *
- * \param[in,out] testData          An object, containing all the data structures needed by SETTLE.
- * \param[in]     pbc               Periodic boundary setup.
- * \param[in]     updateVelocities  If the velocities should be updated.
- * \param[in]     calcVirial        If the virial should be computed.
- * \param[in]     testDescription   Brief description that will be printed in case of test failure.
+ * Wraps the actual implementation of SETTLE into common interface.
  */
-void applySettle(SettleTestData*    testData,
-                 t_pbc              pbc,
-                 bool               updateVelocities,
-                 bool               calcVirial,
-                 const std::string& testDescription);
+class ISettleTestRunner
+{
+public:
+    //! Virtual destructor.
+    virtual ~ISettleTestRunner() {}
 
-/*! \brief Apply SETTLE using GPU version of the algorithm
- *
- * Initializes SETTLE object, copied data to the GPU, applies algorithm, copies the data back,
- * destroys the object. The coordinates, velocities and virial are updated in the testData object.
- *
- * \param[in,out] testData          An object, containing all the data structures needed by SETTLE.
- * \param[in]     pbc               Periodic boundary setup.
- * \param[in]     updateVelocities  If the velocities should be updated.
- * \param[in]     calcVirial        If the virial should be computed.
- * \param[in]     testDescription   Brief description that will be printed in case of test failure.
- */
-void applySettleGpu(SettleTestData*    testData,
-                    t_pbc              pbc,
-                    bool               updateVelocities,
-                    bool               calcVirial,
-                    const std::string& testDescription);
+    /*! \brief Apply SETTLE using CPU version of the algorithm
+     *
+     * Initializes SETTLE object, applies algorithm, destroys the object. The coordinates, velocities
+     * and virial are updated in the testData object.
+     *
+     * \param[in,out] testData          An object, containing all the data structures needed by SETTLE.
+     * \param[in]     pbc               Periodic boundary setup.
+     * \param[in]     updateVelocities  If the velocities should be updated.
+     * \param[in]     calcVirial        If the virial should be computed.
+     * \param[in]     testDescription   Brief description that will be printed in case of test failure.
+     */
+    virtual void applySettle(SettleTestData*    testData,
+                             t_pbc              pbc,
+                             bool               updateVelocities,
+                             bool               calcVirial,
+                             const std::string& testDescription) = 0;
+    /*! \brief Get the hardware description
+     *
+     * \returns A string, describing hardware used by the runner.
+     */
+    virtual std::string hardwareDescription() = 0;
+};
+
+// Runner for the CPU implementation of SETTLE.
+class SettleHostTestRunner : public ISettleTestRunner
+{
+public:
+    //! Default constructor.
+    SettleHostTestRunner() {}
+    /*! \brief Apply SETTLE using CPU version of the algorithm
+     *
+     * Initializes SETTLE object, applies algorithm, destroys the object. The coordinates, velocities
+     * and virial are updated in the testData object.
+     *
+     * \param[in,out] testData          An object, containing all the data structures needed by SETTLE.
+     * \param[in]     pbc               Periodic boundary setup.
+     * \param[in]     updateVelocities  If the velocities should be updated.
+     * \param[in]     calcVirial        If the virial should be computed.
+     * \param[in]     testDescription   Brief description that will be printed in case of test failure.
+     */
+    void applySettle(SettleTestData*    testData,
+                     t_pbc              pbc,
+                     bool               updateVelocities,
+                     bool               calcVirial,
+                     const std::string& testDescription) override;
+    /*! \brief Get the hardware description
+     *
+     * \returns "CPU" string.
+     */
+    std::string hardwareDescription() override { return "CPU"; }
+};
+
+// Runner for the GPU implementation of SETTLE.
+class SettleDeviceTestRunner : public ISettleTestRunner
+{
+public:
+    /*! \brief Constructor. Keeps a copy of the hardware context.
+     *
+     * \param[in] testDevice The device hardware context to be used by the runner.
+     */
+    SettleDeviceTestRunner(const TestDevice& testDevice) : testDevice_(testDevice) {}
+    /*! \brief Apply SETTLE using GPU version of the algorithm
+     *
+     * Initializes SETTLE object, copied data to the GPU, applies algorithm, copies the data back,
+     * destroys the object. The coordinates, velocities and virial are updated in the testData object.
+     *
+     * \param[in,out] testData          An object, containing all the data structures needed by SETTLE.
+     * \param[in]     pbc               Periodic boundary setup.
+     * \param[in]     updateVelocities  If the velocities should be updated.
+     * \param[in]     calcVirial        If the virial should be computed.
+     * \param[in]     testDescription   Brief description that will be printed in case of test failure.
+     */
+    void applySettle(SettleTestData*    testData,
+                     t_pbc              pbc,
+                     bool               updateVelocities,
+                     bool               calcVirial,
+                     const std::string& testDescription) override;
+    /*! \brief Get the hardware description
+     *
+     * \returns A string with GPU description.
+     */
+    std::string hardwareDescription() override { return testDevice_.description(); }
+
+private:
+    //! Test test device to be used in the runner.
+    const TestDevice& testDevice_;
+};
 
 } // namespace test
 } // namespace gmx