# 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
#include "gromacs/pbcutil/pbc.h"
#include "gromacs/utility/stringutil.h"
+#include "testutils/test_hardware_environment.h"
#include "testutils/testasserts.h"
#include "constrtestdata.h"
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:
* 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.
*
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
}
}
}
+ //! 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)
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)
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)
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)
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)
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)
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
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_);
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;
}
#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
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);
* 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;
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
#include "gromacs/utility/stringutil.h"
#include "testutils/refdata.h"
+#include "testutils/test_hardware_environment.h"
#include "testutils/testasserts.h"
#include "leapfrogtestdata.h"
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
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
}
};
-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);
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
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_);
#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.";
}
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_;
* 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
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
#include "gromacs/mdlib/tests/watersystem.h"
#include "testutils/refdata.h"
+#include "testutils/test_hardware_environment.h"
#include "testutils/testasserts.h"
#include "settletestdata.h"
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
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.
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();
// 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);
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
{
SettleTestData::SettleTestData(int numSettles) :
+ numSettles_(numSettles),
x_(c_waterPositions.size()),
xPrime_(c_waterPositions.size()),
v_(c_waterPositions.size())
class SettleTestData
{
public:
+ //! Number of settles
+ int numSettles_;
//! Initial (undisturbed) positions
PaddedVector<gmx::RVec> x_;
//! Updated water atom positions to constrain
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_);
#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.";
}
#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);
/*
* 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;
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