From 37969a6d5a36f14a213df01acf7411f743988c1d Mon Sep 17 00:00:00 2001 From: Artem Zhmurov Date: Tue, 6 Oct 2020 11:37:49 +0200 Subject: [PATCH] Use new GPU infrastructure in MDLib tests 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 --- src/gromacs/mdlib/tests/CMakeLists.txt | 2 +- src/gromacs/mdlib/tests/constr.cpp | 236 +++++++++++++----------- src/gromacs/mdlib/tests/constrtestrunners.cpp | 25 +-- src/gromacs/mdlib/tests/constrtestrunners.cu | 14 +- src/gromacs/mdlib/tests/constrtestrunners.h | 107 +++++++++-- src/gromacs/mdlib/tests/leapfrog.cpp | 44 ++--- src/gromacs/mdlib/tests/leapfrogtestrunners.cpp | 5 +- src/gromacs/mdlib/tests/leapfrogtestrunners.cu | 8 +- src/gromacs/mdlib/tests/leapfrogtestrunners.h | 96 ++++++++-- src/gromacs/mdlib/tests/settle.cpp | 48 ++--- src/gromacs/mdlib/tests/settletestdata.cpp | 1 + src/gromacs/mdlib/tests/settletestdata.h | 2 + src/gromacs/mdlib/tests/settletestrunners.cpp | 21 ++- src/gromacs/mdlib/tests/settletestrunners.cu | 29 +-- src/gromacs/mdlib/tests/settletestrunners.h | 139 ++++++++++---- 15 files changed, 482 insertions(+), 295 deletions(-) diff --git a/src/gromacs/mdlib/tests/CMakeLists.txt b/src/gromacs/mdlib/tests/CMakeLists.txt index ab92f50785..465057ea52 100644 --- a/src/gromacs/mdlib/tests/CMakeLists.txt +++ b/src/gromacs/mdlib/tests/CMakeLists.txt @@ -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 diff --git a/src/gromacs/mdlib/tests/constr.cpp b/src/gromacs/mdlib/tests/constr.cpp index ade8dde632..75b691ef07 100644 --- a/src/gromacs/mdlib/tests/constr.cpp +++ b/src/gromacs/mdlib/tests/constr.cpp @@ -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 ConstraintsTestParameters; - -//! Names of all availible runners -std::vector runnersNames; - -//! Method that fills and returns runnersNames to the test macros. -std::vector 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 getRunnersNames() * For some systems, the value for scaled virial tensor is checked against * pre-computed data. */ -class ConstraintsTest : public ::testing::TestWithParam +class ConstraintsTest : public ::testing::TestWithParam { public: //! PBC setups std::unordered_map pbcs_; - //! Algorithms (SHAKE and LINCS) - std::unordered_map 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> getRunners() + { + std::vector> runners; + // Add runners for CPU versions of SHAKE and LINCS + runners.emplace_back(std::make_unique()); + runners.emplace_back(std::make_unique()); + // 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(*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 diff --git a/src/gromacs/mdlib/tests/constrtestrunners.cpp b/src/gromacs/mdlib/tests/constrtestrunners.cpp index 5fca4c06b3..f57a9de20d 100644 --- a/src/gromacs/mdlib/tests/constrtestrunners.cpp +++ b/src/gromacs/mdlib/tests/constrtestrunners.cpp @@ -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 diff --git a/src/gromacs/mdlib/tests/constrtestrunners.cu b/src/gromacs/mdlib/tests/constrtestrunners.cu index 62b713cd7b..88cc8e866d 100644 --- a/src/gromacs/mdlib/tests/constrtestrunners.cu +++ b/src/gromacs/mdlib/tests/constrtestrunners.cu @@ -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(testData->ir_.nLincsIter, testData->ir_.nProjOrder, deviceContext, deviceStream); diff --git a/src/gromacs/mdlib/tests/constrtestrunners.h b/src/gromacs/mdlib/tests/constrtestrunners.h index a9e7156256..d479e5537c 100644 --- a/src/gromacs/mdlib/tests/constrtestrunners.h +++ b/src/gromacs/mdlib/tests/constrtestrunners.h @@ -33,11 +33,12 @@ * 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 * \ingroup module_mdlib @@ -46,6 +47,10 @@ #ifndef GMX_MDLIB_TESTS_CONSTRTESTRUNNERS_H #define GMX_MDLIB_TESTS_CONSTRTESTRUNNERS_H +#include + +#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 " on ", 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 diff --git a/src/gromacs/mdlib/tests/leapfrog.cpp b/src/gromacs/mdlib/tests/leapfrog.cpp index 3018d295ca..79c257abb3 100644 --- a/src/gromacs/mdlib/tests/leapfrog.cpp +++ b/src/gromacs/mdlib/tests/leapfrog.cpp @@ -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 { public: - //! Availiable runners (CPU and GPU versions of the Leap-Frog) - static std::unordered_map 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 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> runners; + // Add runners for CPU version + runners.emplace_back(std::make_unique()); + // 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(*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 diff --git a/src/gromacs/mdlib/tests/leapfrogtestrunners.cpp b/src/gromacs/mdlib/tests/leapfrogtestrunners.cpp index 492fb8e080..2978665cb4 100644 --- a/src/gromacs/mdlib/tests/leapfrogtestrunners.cpp +++ b/src/gromacs/mdlib/tests/leapfrogtestrunners.cpp @@ -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."; } diff --git a/src/gromacs/mdlib/tests/leapfrogtestrunners.cu b/src/gromacs/mdlib/tests/leapfrogtestrunners.cu index 7f9a5766a3..7089794f5a 100644 --- a/src/gromacs/mdlib/tests/leapfrogtestrunners.cu +++ b/src/gromacs/mdlib/tests/leapfrogtestrunners.cu @@ -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_; diff --git a/src/gromacs/mdlib/tests/leapfrogtestrunners.h b/src/gromacs/mdlib/tests/leapfrogtestrunners.h index bef6c49d1a..deac1d0a68 100644 --- a/src/gromacs/mdlib/tests/leapfrogtestrunners.h +++ b/src/gromacs/mdlib/tests/leapfrogtestrunners.h @@ -33,17 +33,23 @@ * 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 * \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 diff --git a/src/gromacs/mdlib/tests/settle.cpp b/src/gromacs/mdlib/tests/settle.cpp index 9dc2d9d505..e565b52848 100644 --- a/src/gromacs/mdlib/tests/settle.cpp +++ b/src/gromacs/mdlib/tests/settle.cpp @@ -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 public: //! PBC setups std::unordered_map pbcs_; - //! Runners (CPU and GPU versions of SETTLE) - std::unordered_map - 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> runners; + // Add runners for CPU version + runners.emplace_back(std::make_unique()); + // 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(*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 diff --git a/src/gromacs/mdlib/tests/settletestdata.cpp b/src/gromacs/mdlib/tests/settletestdata.cpp index af9c9cc7ee..158db19fe7 100644 --- a/src/gromacs/mdlib/tests/settletestdata.cpp +++ b/src/gromacs/mdlib/tests/settletestdata.cpp @@ -73,6 +73,7 @@ namespace test { SettleTestData::SettleTestData(int numSettles) : + numSettles_(numSettles), x_(c_waterPositions.size()), xPrime_(c_waterPositions.size()), v_(c_waterPositions.size()) diff --git a/src/gromacs/mdlib/tests/settletestdata.h b/src/gromacs/mdlib/tests/settletestdata.h index fc115f1a22..45d1c946b8 100644 --- a/src/gromacs/mdlib/tests/settletestdata.h +++ b/src/gromacs/mdlib/tests/settletestdata.h @@ -62,6 +62,8 @@ namespace test class SettleTestData { public: + //! Number of settles + int numSettles_; //! Initial (undisturbed) positions PaddedVector x_; //! Updated water atom positions to constrain diff --git a/src/gromacs/mdlib/tests/settletestrunners.cpp b/src/gromacs/mdlib/tests/settletestrunners.cpp index 3fd90dc689..cfc2b0ca20 100644 --- a/src/gromacs/mdlib/tests/settletestrunners.cpp +++ b/src/gromacs/mdlib/tests/settletestrunners.cpp @@ -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."; } diff --git a/src/gromacs/mdlib/tests/settletestrunners.cu b/src/gromacs/mdlib/tests/settletestrunners.cu index 930f4cb5ba..1200626ad0 100644 --- a/src/gromacs/mdlib/tests/settletestrunners.cu +++ b/src/gromacs/mdlib/tests/settletestrunners.cu @@ -56,35 +56,26 @@ #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(testData->mtop_, deviceContext, deviceStream); diff --git a/src/gromacs/mdlib/tests/settletestrunners.h b/src/gromacs/mdlib/tests/settletestrunners.h index 0876121375..3f643f605e 100644 --- a/src/gromacs/mdlib/tests/settletestrunners.h +++ b/src/gromacs/mdlib/tests/settletestrunners.h @@ -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. @@ -33,18 +33,23 @@ * 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 * \ingroup module_mdlib */ - #ifndef GMX_MDLIB_TESTS_SETTLETESTRUNNERS_H #define GMX_MDLIB_TESTS_SETTLETESTRUNNERS_H +#include + +#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 -- 2.11.4.GIT