#include "pme-spline-work.h"
#include "pme-spread.h"
+/*! \brief Help build a descriptive message in \c error if there are
+ * \c errorReasons why PME on GPU is not supported.
+ *
+ * \returns Whether the lack of errorReasons indicate there is support. */
+static bool
+addMessageIfNotSupported(const std::list<std::string> &errorReasons,
+ std::string *error)
+{
+ bool foundErrorReasons = errorReasons.empty();
+ if (!foundErrorReasons && error)
+ {
+ std::string regressionTestMarker = "PME GPU does not support";
+ // this prefix is tested for in the regression tests script gmxtest.pl
+ *error = regressionTestMarker + ": " + gmx::joinStrings(errorReasons, "; ") + ".";
+ }
+ return foundErrorReasons;
+}
+
+bool pme_gpu_supports_build(std::string *error)
+{
+ std::list<std::string> errorReasons;
+ if (GMX_DOUBLE)
+ {
+ errorReasons.push_back("double precision");
+ }
+ if (GMX_GPU != GMX_GPU_CUDA)
+ {
+ errorReasons.push_back("non-CUDA build of GROMACS");
+ }
+ return addMessageIfNotSupported(errorReasons, error);
+}
+
bool pme_gpu_supports_input(const t_inputrec *ir, std::string *error)
{
std::list<std::string> errorReasons;
{
errorReasons.push_back("Lennard-Jones PME");
}
-#if GMX_DOUBLE
- {
- errorReasons.push_back("double precision");
- }
-#endif
-#if GMX_GPU != GMX_GPU_CUDA
- {
- errorReasons.push_back("non-CUDA build of GROMACS");
- }
-#endif
if (ir->cutoff_scheme == ecutsGROUP)
{
errorReasons.push_back("group cutoff scheme");
{
errorReasons.push_back("test particle insertion");
}
-
- bool inputSupported = errorReasons.empty();
- if (!inputSupported && error)
- {
- std::string regressionTestMarker = "PME GPU does not support";
- // this prefix is tested for in the regression tests script gmxtest.pl
- *error = regressionTestMarker + ": " + gmx::joinStrings(errorReasons, "; ") + ".";
- }
- return inputSupported;
+ return addMessageIfNotSupported(errorReasons, error);
}
/*! \brief \libinternal
{
errorReasons.push_back("Lennard-Jones PME");
}
-#if GMX_DOUBLE
+ if (GMX_DOUBLE)
{
errorReasons.push_back("double precision");
}
-#endif
-#if GMX_GPU != GMX_GPU_CUDA
+ if (GMX_GPU != GMX_GPU_CUDA)
{
errorReasons.push_back("non-CUDA build of GROMACS");
}
-#endif
- bool inputSupported = errorReasons.empty();
- if (!inputSupported && error)
- {
- std::string regressionTestMarker = "PME GPU does not support";
- // this prefix is tested for in the regression tests script gmxtest.pl
- *error = regressionTestMarker + ": " + gmx::joinStrings(errorReasons, "; ") + ".";
- }
- return inputSupported;
+ return addMessageIfNotSupported(errorReasons, error);
}
PmeRunMode pme_run_mode(const gmx_pme_t *pme)
/* A block of PME GPU functions */
+/*! \brief Checks whether the GROMACS build allows to run PME on GPU.
+ * TODO: this partly duplicates an internal PME assert function
+ * pme_gpu_check_restrictions(), except that works with a
+ * formed gmx_pme_t structure. Should that one go away/work with inputrec?
+ *
+ * \param[out] error If non-null, the error message when PME is not supported on GPU.
+ *
+ * \returns true if PME can run on GPU on this build, false otherwise.
+ */
+bool pme_gpu_supports_build(std::string *error);
+
/*! \brief Checks whether the input system allows to run PME on GPU.
- * TODO: this mostly duplicates an internal PME assert function
+ * TODO: this partly duplicates an internal PME assert function
* pme_gpu_check_restrictions(), except that works with a
* formed gmx_pme_t structure. Should that one go away/work with inputrec?
*
* \param[in] ir Input system.
- * \param[out] error The error message if the input is not supported on GPU.
+ * \param[out] error If non-null, the error message if the input is not supported on GPU.
*
* \returns true if PME can run on GPU with this input, false otherwise.
*/
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2016,2017,2018, 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.
inputRec.coulombtype = eelPME;
inputRec.epsilon_r = 1.0;
- TestReferenceData refData;
- const std::map<CodePath, std::string> modesToTest = {{CodePath::CPU, "CPU"},
- {CodePath::CUDA, "CUDA"}};
- for (const auto &mode : modesToTest)
+ TestReferenceData refData;
+ for (const auto &context : getPmeTestEnv()->getHardwareContexts())
{
- const bool supportedInput = pmeSupportsInputForMode(&inputRec, mode.first);
+ CodePath codePath = context.getCodePath();
+ const bool supportedInput = pmeSupportsInputForMode(&inputRec, codePath);
if (!supportedInput)
{
/* Testing the failure for the unsupported input */
- EXPECT_THROW(pmeInitAtoms(&inputRec, mode.first, nullptr, inputAtomData.coordinates, inputAtomData.charges, box), NotImplementedError);
+ EXPECT_THROW(pmeInitAtoms(&inputRec, codePath, nullptr, inputAtomData.coordinates, inputAtomData.charges, box), NotImplementedError);
continue;
}
- const auto contextsToTest = getPmeTestEnv()->getHardwareContexts(mode.first);
- for (const auto &context : contextsToTest)
+ /* Describing the test uniquely */
+ SCOPED_TRACE(formatString("Testing force gathering with %s %sfor PME grid size %d %d %d"
+ ", order %d, %zu atoms, %s",
+ codePathToString(codePath), context.getDescription().c_str(),
+ gridSize[XX], gridSize[YY], gridSize[ZZ],
+ pmeOrder,
+ atomCount,
+ (inputForceTreatment == PmeForceOutputHandling::ReduceWithInput) ? "with reduction" : "without reduction"
+ ));
+
+ PmeSafePointer pmeSafe = pmeInitAtoms(&inputRec, codePath, context.getDeviceInfo(), inputAtomData.coordinates, inputAtomData.charges, box);
+
+ /* Setting some more inputs */
+ pmeSetRealGrid(pmeSafe.get(), codePath, nonZeroGridValues);
+ pmeSetGridLineIndices(pmeSafe.get(), codePath, inputAtomData.gridLineIndices);
+ for (int dimIndex = 0; dimIndex < DIM; dimIndex++)
{
- /* Describing the test uniquely */
- SCOPED_TRACE(formatString("Testing force gathering with %s %sfor PME grid size %d %d %d"
- ", order %d, %zu atoms, %s",
- mode.second.c_str(), context.getDescription().c_str(),
- gridSize[XX], gridSize[YY], gridSize[ZZ],
- pmeOrder,
- atomCount,
- (inputForceTreatment == PmeForceOutputHandling::ReduceWithInput) ? "with reduction" : "without reduction"
- ));
-
- PmeSafePointer pmeSafe = pmeInitAtoms(&inputRec, mode.first, context.getDeviceInfo(), inputAtomData.coordinates, inputAtomData.charges, box);
-
- /* Setting some more inputs */
- pmeSetRealGrid(pmeSafe.get(), mode.first, nonZeroGridValues);
- pmeSetGridLineIndices(pmeSafe.get(), mode.first, inputAtomData.gridLineIndices);
- for (int dimIndex = 0; dimIndex < DIM; dimIndex++)
- {
- pmeSetSplineData(pmeSafe.get(), mode.first, inputAtomSplineData.splineValues[dimIndex], PmeSplineDataType::Values, dimIndex);
- pmeSetSplineData(pmeSafe.get(), mode.first, inputAtomSplineData.splineDerivatives[dimIndex], PmeSplineDataType::Derivatives, dimIndex);
- }
+ pmeSetSplineData(pmeSafe.get(), codePath, inputAtomSplineData.splineValues[dimIndex], PmeSplineDataType::Values, dimIndex);
+ pmeSetSplineData(pmeSafe.get(), codePath, inputAtomSplineData.splineDerivatives[dimIndex], PmeSplineDataType::Derivatives, dimIndex);
+ }
- /* Explicitly copying the sample forces to be able to modify them */
- auto inputForcesFull(c_sampleForcesFull);
- GMX_RELEASE_ASSERT(inputForcesFull.size() >= atomCount, "Bad input forces size");
- auto forces = ForcesVector(inputForcesFull).subArray(0, atomCount);
+ /* Explicitly copying the sample forces to be able to modify them */
+ auto inputForcesFull(c_sampleForcesFull);
+ GMX_RELEASE_ASSERT(inputForcesFull.size() >= atomCount, "Bad input forces size");
+ auto forces = ForcesVector(inputForcesFull).subArray(0, atomCount);
- /* Running the force gathering itself */
- pmePerformGather(pmeSafe.get(), mode.first, inputForceTreatment, forces);
- pmeFinalizeTest(pmeSafe.get(), mode.first);
+ /* Running the force gathering itself */
+ pmePerformGather(pmeSafe.get(), codePath, inputForceTreatment, forces);
+ pmeFinalizeTest(pmeSafe.get(), codePath);
- /* Check the output forces correctness */
- TestReferenceChecker forceChecker(refData.rootChecker());
- const auto ulpTolerance = 3 * pmeOrder;
- forceChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpTolerance));
- forceChecker.checkSequence(forces.begin(), forces.end(), "Forces");
- }
+ /* Check the output forces correctness */
+ TestReferenceChecker forceChecker(refData.rootChecker());
+ const auto ulpTolerance = 3 * pmeOrder;
+ forceChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpTolerance));
+ forceChecker.checkSequence(forces.begin(), forces.end(), "Forces");
}
}
};
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2016,2017,2018, 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.
GMX_THROW(InternalError("Unknown PME solver"));
}
- TestReferenceData refData;
- const std::map<CodePath, std::string> modesToTest = {{CodePath::CPU, "CPU"},
- {CodePath::CUDA, "CUDA"}};
- for (const auto &mode : modesToTest)
+ TestReferenceData refData;
+ for (const auto &context : getPmeTestEnv()->getHardwareContexts())
{
- const bool supportedInput = pmeSupportsInputForMode(&inputRec, mode.first);
+ CodePath codePath = context.getCodePath();
+ const bool supportedInput = pmeSupportsInputForMode(&inputRec, codePath);
if (!supportedInput)
{
/* Testing the failure for the unsupported input */
- EXPECT_THROW(pmeInitEmpty(&inputRec, mode.first, nullptr, box, ewaldCoeff_q, ewaldCoeff_lj), NotImplementedError);
+ EXPECT_THROW(pmeInitEmpty(&inputRec, codePath, nullptr, box, ewaldCoeff_q, ewaldCoeff_lj), NotImplementedError);
continue;
}
std::map<GridOrdering, std::string> gridOrderingsToTest = {{GridOrdering::YZX, "YZX"}};
- if (mode.first == CodePath::CUDA)
+ if (codePath == CodePath::CUDA)
{
gridOrderingsToTest[GridOrdering::XYZ] = "XYZ";
}
- const auto contextsToTest = getPmeTestEnv()->getHardwareContexts(mode.first);
for (const auto &gridOrdering : gridOrderingsToTest)
{
- for (const auto &context : contextsToTest)
+ for (bool computeEnergyAndVirial : {false, true})
{
- for (bool computeEnergyAndVirial : {false, true})
+ /* Describing the test*/
+ SCOPED_TRACE(formatString("Testing solving (%s, %s, %s energy/virial) with %s %sfor PME grid size %d %d %d, Ewald coefficients %g %g",
+ (method == PmeSolveAlgorithm::LennardJones) ? "Lennard-Jones" : "Coulomb",
+ gridOrdering.second.c_str(),
+ computeEnergyAndVirial ? "with" : "without",
+ codePathToString(codePath),
+ context.getDescription().c_str(),
+ gridSize[XX], gridSize[YY], gridSize[ZZ],
+ ewaldCoeff_q, ewaldCoeff_lj
+ ));
+
+ /* Running the test */
+ PmeSafePointer pmeSafe = pmeInitEmpty(&inputRec, codePath, context.getDeviceInfo(), box, ewaldCoeff_q, ewaldCoeff_lj);
+ pmeSetComplexGrid(pmeSafe.get(), codePath, gridOrdering.first, nonZeroGridValues);
+ const real cellVolume = box[0] * box[4] * box[8];
+ //FIXME - this is box[XX][XX] * box[YY][YY] * box[ZZ][ZZ], should be stored in the PME structure
+ pmePerformSolve(pmeSafe.get(), codePath, method, cellVolume, gridOrdering.first, computeEnergyAndVirial);
+ pmeFinalizeTest(pmeSafe.get(), codePath);
+
+ /* Check the outputs */
+ TestReferenceChecker checker(refData.rootChecker());
+
+ SparseComplexGridValuesOutput nonZeroGridValuesOutput = pmeGetComplexGrid(pmeSafe.get(), codePath, gridOrdering.first);
+ /* Transformed grid */
+ TestReferenceChecker gridValuesChecker(checker.checkCompound("NonZeroGridValues", "ComplexSpaceGrid"));
+
+ real gridValuesMagnitude = 1.0;
+ for (const auto &point : nonZeroGridValuesOutput)
{
- /* Describing the test*/
- SCOPED_TRACE(formatString("Testing solving (%s, %s, %s energy/virial) with %s %sfor PME grid size %d %d %d, Ewald coefficients %g %g",
- (method == PmeSolveAlgorithm::LennardJones) ? "Lennard-Jones" : "Coulomb",
- gridOrdering.second.c_str(),
- computeEnergyAndVirial ? "with" : "without",
- mode.second.c_str(),
- context.getDescription().c_str(),
- gridSize[XX], gridSize[YY], gridSize[ZZ],
- ewaldCoeff_q, ewaldCoeff_lj
- ));
-
- /* Running the test */
- PmeSafePointer pmeSafe = pmeInitEmpty(&inputRec, mode.first, context.getDeviceInfo(), box, ewaldCoeff_q, ewaldCoeff_lj);
- pmeSetComplexGrid(pmeSafe.get(), mode.first, gridOrdering.first, nonZeroGridValues);
- const real cellVolume = box[0] * box[4] * box[8];
- //FIXME - this is box[XX][XX] * box[YY][YY] * box[ZZ][ZZ], should be stored in the PME structure
- pmePerformSolve(pmeSafe.get(), mode.first, method, cellVolume, gridOrdering.first, computeEnergyAndVirial);
- pmeFinalizeTest(pmeSafe.get(), mode.first);
-
- /* Check the outputs */
- TestReferenceChecker checker(refData.rootChecker());
-
- SparseComplexGridValuesOutput nonZeroGridValuesOutput = pmeGetComplexGrid(pmeSafe.get(), mode.first, gridOrdering.first);
- /* Transformed grid */
- TestReferenceChecker gridValuesChecker(checker.checkCompound("NonZeroGridValues", "ComplexSpaceGrid"));
-
- real gridValuesMagnitude = 1.0;
- for (const auto &point : nonZeroGridValuesOutput)
- {
- gridValuesMagnitude = std::max(std::fabs(point.second.re), gridValuesMagnitude);
- gridValuesMagnitude = std::max(std::fabs(point.second.im), gridValuesMagnitude);
- }
- // Spline moduli participate 3 times in the computation; 2 is an additional factor for SIMD exp() precision
- gmx_uint64_t gridUlpToleranceFactor = DIM * 2;
- if (method == PmeSolveAlgorithm::LennardJones)
+ gridValuesMagnitude = std::max(std::fabs(point.second.re), gridValuesMagnitude);
+ gridValuesMagnitude = std::max(std::fabs(point.second.im), gridValuesMagnitude);
+ }
+ // Spline moduli participate 3 times in the computation; 2 is an additional factor for SIMD exp() precision
+ gmx_uint64_t gridUlpToleranceFactor = DIM * 2;
+ if (method == PmeSolveAlgorithm::LennardJones)
+ {
+ // Lennard Jones is more complex and also uses erfc(), relax more
+ gridUlpToleranceFactor *= 2;
+ }
+ const gmx_uint64_t splineModuliDoublePrecisionUlps
+ = getSplineModuliDoublePrecisionUlps(inputRec.pme_order + 1);
+ auto gridTolerance
+ = relativeToleranceAsPrecisionDependentUlp(gridValuesMagnitude,
+ gridUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
+ gridUlpToleranceFactor * splineModuliDoublePrecisionUlps);
+ gridValuesChecker.setDefaultTolerance(gridTolerance);
+
+ for (const auto &point : nonZeroGridValuesOutput)
+ {
+ // we want an additional safeguard for denormal numbers as they cause an exception in string conversion;
+ // however, using GMX_REAL_MIN causes an "unused item warning" for single precision builds
+ if (fabs(point.second.re) >= GMX_FLOAT_MIN)
{
- // Lennard Jones is more complex and also uses erfc(), relax more
- gridUlpToleranceFactor *= 2;
+ gridValuesChecker.checkReal(point.second.re, (point.first + " re").c_str());
}
- const gmx_uint64_t splineModuliDoublePrecisionUlps
- = getSplineModuliDoublePrecisionUlps(inputRec.pme_order + 1);
- auto gridTolerance
- = relativeToleranceAsPrecisionDependentUlp(gridValuesMagnitude,
- gridUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
- gridUlpToleranceFactor * splineModuliDoublePrecisionUlps);
- gridValuesChecker.setDefaultTolerance(gridTolerance);
-
- for (const auto &point : nonZeroGridValuesOutput)
+ if (fabs(point.second.im) >= GMX_FLOAT_MIN)
{
- // we want an additional safeguard for denormal numbers as they cause an exception in string conversion;
- // however, using GMX_REAL_MIN causes an "unused item warning" for single precision builds
- if (fabs(point.second.re) >= GMX_FLOAT_MIN)
- {
- gridValuesChecker.checkReal(point.second.re, (point.first + " re").c_str());
- }
- if (fabs(point.second.im) >= GMX_FLOAT_MIN)
- {
- gridValuesChecker.checkReal(point.second.im, (point.first + " im").c_str());
- }
+ gridValuesChecker.checkReal(point.second.im, (point.first + " im").c_str());
}
+ }
- if (computeEnergyAndVirial)
+ if (computeEnergyAndVirial)
+ {
+ // Extract the energy and virial
+ real energy;
+ Matrix3x3 virial;
+ std::tie(energy, virial) = pmeGetReciprocalEnergyAndVirial(pmeSafe.get(), codePath, method);
+
+ // These quantities are computed based on the grid values, so must have
+ // checking relative tolerances at least as large. Virial needs more flops
+ // than energy, so needs a larger tolerance.
+
+ /* Energy */
+ double energyMagnitude = 10.0;
+ // TODO This factor is arbitrary, do a proper error-propagation analysis
+ gmx_uint64_t energyUlpToleranceFactor = gridUlpToleranceFactor * 2;
+ auto energyTolerance
+ = relativeToleranceAsPrecisionDependentUlp(energyMagnitude,
+ energyUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
+ energyUlpToleranceFactor * splineModuliDoublePrecisionUlps);
+ TestReferenceChecker energyChecker(checker);
+ energyChecker.setDefaultTolerance(energyTolerance);
+ energyChecker.checkReal(energy, "Energy");
+
+ /* Virial */
+ double virialMagnitude = 1000.0;
+ // TODO This factor is arbitrary, do a proper error-propagation analysis
+ gmx_uint64_t virialUlpToleranceFactor = energyUlpToleranceFactor * 2;
+ auto virialTolerance
+ = relativeToleranceAsPrecisionDependentUlp(virialMagnitude,
+ virialUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
+ virialUlpToleranceFactor * splineModuliDoublePrecisionUlps);
+ TestReferenceChecker virialChecker(checker.checkCompound("Matrix", "Virial"));
+ virialChecker.setDefaultTolerance(virialTolerance);
+ for (int i = 0; i < DIM; i++)
{
- // Extract the energy and virial
- real energy;
- Matrix3x3 virial;
- std::tie(energy, virial) = pmeGetReciprocalEnergyAndVirial(pmeSafe.get(), mode.first, method);
-
- // These quantities are computed based on the grid values, so must have
- // checking relative tolerances at least as large. Virial needs more flops
- // than energy, so needs a larger tolerance.
-
- /* Energy */
- double energyMagnitude = 10.0;
- // TODO This factor is arbitrary, do a proper error-propagation analysis
- gmx_uint64_t energyUlpToleranceFactor = gridUlpToleranceFactor * 2;
- auto energyTolerance
- = relativeToleranceAsPrecisionDependentUlp(energyMagnitude,
- energyUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
- energyUlpToleranceFactor * splineModuliDoublePrecisionUlps);
- TestReferenceChecker energyChecker(checker);
- energyChecker.setDefaultTolerance(energyTolerance);
- energyChecker.checkReal(energy, "Energy");
-
- /* Virial */
- double virialMagnitude = 1000.0;
- // TODO This factor is arbitrary, do a proper error-propagation analysis
- gmx_uint64_t virialUlpToleranceFactor = energyUlpToleranceFactor * 2;
- auto virialTolerance
- = relativeToleranceAsPrecisionDependentUlp(virialMagnitude,
- virialUlpToleranceFactor * c_splineModuliSinglePrecisionUlps,
- virialUlpToleranceFactor * splineModuliDoublePrecisionUlps);
- TestReferenceChecker virialChecker(checker.checkCompound("Matrix", "Virial"));
- virialChecker.setDefaultTolerance(virialTolerance);
- for (int i = 0; i < DIM; i++)
+ for (int j = 0; j <= i; j++)
{
- for (int j = 0; j <= i; j++)
- {
- std::string valueId = formatString("Cell %d %d", i, j);
- virialChecker.checkReal(virial[i * DIM + j], valueId.c_str());
- }
+ std::string valueId = formatString("Cell %d %d", i, j);
+ virialChecker.checkReal(virial[i * DIM + j], valueId.c_str());
}
}
}
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2016,2017, by the GROMACS development team, led by
+ * Copyright (c) 2016,2017,2018, 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.
TestReferenceData refData;
- const std::map<CodePath, std::string> modesToTest = {{CodePath::CPU, "CPU"},
- {CodePath::CUDA, "CUDA"}};
-
const std::map<PmeSplineAndSpreadOptions, std::string> optionsToTest = {{PmeSplineAndSpreadOptions::SplineAndSpreadUnified, "spline computation and charge spreading (fused)"},
{PmeSplineAndSpreadOptions::SplineOnly, "spline computation"},
{PmeSplineAndSpreadOptions::SpreadOnly, "charge spreading"}};
// This is just a hack for a single specific output though.
// What would be much better TODO is to split different codepaths into separate tests,
// while making them use the same reference files.
- bool gridValuesSizeAssigned = false;
- size_t previousGridValuesSize;
+ bool gridValuesSizeAssigned = false;
+ size_t previousGridValuesSize;
- for (const auto &mode : modesToTest)
+ for (const auto &context : getPmeTestEnv()->getHardwareContexts())
{
- const bool supportedInput = pmeSupportsInputForMode(&inputRec, mode.first);
+ CodePath codePath = context.getCodePath();
+ const bool supportedInput = pmeSupportsInputForMode(&inputRec, codePath);
if (!supportedInput)
{
/* Testing the failure for the unsupported input */
- EXPECT_THROW(pmeInitAtoms(&inputRec, mode.first, nullptr, coordinates, charges, box), NotImplementedError);
+ EXPECT_THROW(pmeInitAtoms(&inputRec, codePath, nullptr, coordinates, charges, box), NotImplementedError);
continue;
}
- const auto contextsToTest = getPmeTestEnv()->getHardwareContexts(mode.first);
- for (const auto &context : contextsToTest)
+ for (const auto &option : optionsToTest)
{
- for (const auto &option : optionsToTest)
+ /* Describing the test uniquely in case it fails */
+
+ SCOPED_TRACE(formatString("Testing %s with %s %sfor PME grid size %d %d %d"
+ ", order %d, %zu atoms",
+ option.second.c_str(), codePathToString(codePath),
+ context.getDescription().c_str(),
+ gridSize[XX], gridSize[YY], gridSize[ZZ],
+ pmeOrder,
+ atomCount));
+
+ /* Running the test */
+
+ PmeSafePointer pmeSafe = pmeInitAtoms(&inputRec, codePath, context.getDeviceInfo(), coordinates, charges, box);
+
+ const bool computeSplines = (option.first == PmeSplineAndSpreadOptions::SplineOnly) || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
+ const bool spreadCharges = (option.first == PmeSplineAndSpreadOptions::SpreadOnly) || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
+
+ if (!computeSplines)
{
- /* Describing the test uniquely in case it fails */
+ // Here we should set up the results of the spline computation so that the spread can run.
+ // What is lazy and works is running the separate spline so that it will set it up for us:
+ pmePerformSplineAndSpread(pmeSafe.get(), codePath, true, false);
+ // We know that it is tested in another iteration.
+ // TODO: Clean alternative: read and set the reference gridline indices, spline params
+ }
- SCOPED_TRACE(formatString("Testing %s with %s %sfor PME grid size %d %d %d"
- ", order %d, %zu atoms",
- option.second.c_str(), mode.second.c_str(),
- context.getDescription().c_str(),
- gridSize[XX], gridSize[YY], gridSize[ZZ],
- pmeOrder,
- atomCount));
+ pmePerformSplineAndSpread(pmeSafe.get(), codePath, computeSplines, spreadCharges);
+ pmeFinalizeTest(pmeSafe.get(), codePath);
- /* Running the test */
+ /* Outputs correctness check */
+ /* All tolerances were picked empirically for single precision on CPU */
- PmeSafePointer pmeSafe = pmeInitAtoms(&inputRec, mode.first, context.getDeviceInfo(), coordinates, charges, box);
+ TestReferenceChecker rootChecker(refData.rootChecker());
- const bool computeSplines = (option.first == PmeSplineAndSpreadOptions::SplineOnly) || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
- const bool spreadCharges = (option.first == PmeSplineAndSpreadOptions::SpreadOnly) || (option.first == PmeSplineAndSpreadOptions::SplineAndSpreadUnified);
+ const auto maxGridSize = std::max(std::max(gridSize[XX], gridSize[YY]), gridSize[ZZ]);
+ const auto ulpToleranceSplineValues = 4 * (pmeOrder - 2) * maxGridSize;
+ /* 4 is a modest estimate for amount of operations; (pmeOrder - 2) is a number of iterations;
+ * maxGridSize is inverse of the smallest positive fractional coordinate (which are interpolated by the splines).
+ */
+
+ if (computeSplines)
+ {
+ const char *dimString[] = { "X", "Y", "Z" };
- if (!computeSplines)
+ /* Spline values */
+ SCOPED_TRACE(formatString("Testing spline values with tolerance of %ld", ulpToleranceSplineValues));
+ TestReferenceChecker splineValuesChecker(rootChecker.checkCompound("Splines", "Values"));
+ splineValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceSplineValues));
+ for (int i = 0; i < DIM; i++)
{
- // Here we should set up the results of the spline computation so that the spread can run.
- // What is lazy and works is running the separate spline so that it will set it up for us:
- pmePerformSplineAndSpread(pmeSafe.get(), mode.first, true, false);
- // We know that it is tested in another iteration.
- // TODO: Clean alternative: read and set the reference gridline indices, spline params
+ auto splineValuesDim = pmeGetSplineData(pmeSafe.get(), codePath, PmeSplineDataType::Values, i);
+ splineValuesChecker.checkSequence(splineValuesDim.begin(), splineValuesDim.end(), dimString[i]);
}
- pmePerformSplineAndSpread(pmeSafe.get(), mode.first, computeSplines, spreadCharges);
- pmeFinalizeTest(pmeSafe.get(), mode.first);
-
- /* Outputs correctness check */
- /* All tolerances were picked empirically for single precision on CPU */
-
- TestReferenceChecker rootChecker(refData.rootChecker());
+ /* Spline derivatives */
+ const auto ulpToleranceSplineDerivatives = 4 * ulpToleranceSplineValues;
+ /* 4 is just a wild guess since the derivatives are deltas of neighbor spline values which could differ greatly */
+ SCOPED_TRACE(formatString("Testing spline derivatives with tolerance of %ld", ulpToleranceSplineDerivatives));
+ TestReferenceChecker splineDerivativesChecker(rootChecker.checkCompound("Splines", "Derivatives"));
+ splineDerivativesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceSplineDerivatives));
+ for (int i = 0; i < DIM; i++)
+ {
+ auto splineDerivativesDim = pmeGetSplineData(pmeSafe.get(), codePath, PmeSplineDataType::Derivatives, i);
+ splineDerivativesChecker.checkSequence(splineDerivativesDim.begin(), splineDerivativesDim.end(), dimString[i]);
+ }
- const auto maxGridSize = std::max(std::max(gridSize[XX], gridSize[YY]), gridSize[ZZ]);
- const auto ulpToleranceSplineValues = 4 * (pmeOrder - 2) * maxGridSize;
- /* 4 is a modest estimate for amount of operations; (pmeOrder - 2) is a number of iterations;
- * maxGridSize is inverse of the smallest positive fractional coordinate (which are interpolated by the splines).
- */
+ /* Particle gridline indices */
+ auto gridLineIndices = pmeGetGridlineIndices(pmeSafe.get(), codePath);
+ rootChecker.checkSequence(gridLineIndices.begin(), gridLineIndices.end(), "Gridline indices");
+ }
- if (computeSplines)
+ if (spreadCharges)
+ {
+ /* The wrapped grid */
+ SparseRealGridValuesOutput nonZeroGridValues = pmeGetRealGrid(pmeSafe.get(), codePath);
+ TestReferenceChecker gridValuesChecker(rootChecker.checkCompound("NonZeroGridValues", "RealSpaceGrid"));
+ const auto ulpToleranceGrid = 2 * ulpToleranceSplineValues * (int)(ceil(sqrt(atomCount)));
+ /* 2 is empiric; sqrt(atomCount) assumes all the input charges may spread onto the same cell */
+ SCOPED_TRACE(formatString("Testing grid values with tolerance of %ld", ulpToleranceGrid));
+ if (!gridValuesSizeAssigned)
+ {
+ previousGridValuesSize = nonZeroGridValues.size();
+ gridValuesSizeAssigned = true;
+ }
+ else
{
- const char *dimString[] = { "X", "Y", "Z" };
-
- /* Spline values */
- SCOPED_TRACE(formatString("Testing spline values with tolerance of %ld", ulpToleranceSplineValues));
- TestReferenceChecker splineValuesChecker(rootChecker.checkCompound("Splines", "Values"));
- splineValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceSplineValues));
- for (int i = 0; i < DIM; i++)
- {
- auto splineValuesDim = pmeGetSplineData(pmeSafe.get(), mode.first, PmeSplineDataType::Values, i);
- splineValuesChecker.checkSequence(splineValuesDim.begin(), splineValuesDim.end(), dimString[i]);
- }
-
- /* Spline derivatives */
- const auto ulpToleranceSplineDerivatives = 4 * ulpToleranceSplineValues;
- /* 4 is just a wild guess since the derivatives are deltas of neighbor spline values which could differ greatly */
- SCOPED_TRACE(formatString("Testing spline derivatives with tolerance of %ld", ulpToleranceSplineDerivatives));
- TestReferenceChecker splineDerivativesChecker(rootChecker.checkCompound("Splines", "Derivatives"));
- splineDerivativesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceSplineDerivatives));
- for (int i = 0; i < DIM; i++)
- {
- auto splineDerivativesDim = pmeGetSplineData(pmeSafe.get(), mode.first, PmeSplineDataType::Derivatives, i);
- splineDerivativesChecker.checkSequence(splineDerivativesDim.begin(), splineDerivativesDim.end(), dimString[i]);
- }
-
- /* Particle gridline indices */
- auto gridLineIndices = pmeGetGridlineIndices(pmeSafe.get(), mode.first);
- rootChecker.checkSequence(gridLineIndices.begin(), gridLineIndices.end(), "Gridline indices");
+ EXPECT_EQ(previousGridValuesSize, nonZeroGridValues.size());
}
- if (spreadCharges)
+ gridValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceGrid));
+ for (const auto &point : nonZeroGridValues)
{
- /* The wrapped grid */
- SparseRealGridValuesOutput nonZeroGridValues = pmeGetRealGrid(pmeSafe.get(), mode.first);
- TestReferenceChecker gridValuesChecker(rootChecker.checkCompound("NonZeroGridValues", "RealSpaceGrid"));
- const auto ulpToleranceGrid = 2 * ulpToleranceSplineValues * (int)(ceil(sqrt(atomCount)));
- /* 2 is empiric; sqrt(atomCount) assumes all the input charges may spread onto the same cell */
- SCOPED_TRACE(formatString("Testing grid values with tolerance of %ld", ulpToleranceGrid));
- if (!gridValuesSizeAssigned)
- {
- previousGridValuesSize = nonZeroGridValues.size();
- gridValuesSizeAssigned = true;
- }
- else
- {
- EXPECT_EQ(previousGridValuesSize, nonZeroGridValues.size());
- }
-
- gridValuesChecker.setDefaultTolerance(relativeToleranceAsUlp(1.0, ulpToleranceGrid));
- for (const auto &point : nonZeroGridValues)
- {
- gridValuesChecker.checkReal(point.second, point.first.c_str());
- }
+ gridValuesChecker.checkReal(point.second, point.first.c_str());
}
}
}
break;
case CodePath::CUDA:
- implemented = pme_gpu_supports_input(inputRec, nullptr);
+ implemented = (pme_gpu_supports_build(nullptr) &&
+ pme_gpu_supports_input(inputRec, nullptr));
break;
default:
#include "testhardwarecontexts.h"
-#include "config.h"
-
+#include "gromacs/ewald/pme.h"
#include "gromacs/gpu_utils/gpu_utils.h"
#include "gromacs/hardware/hw_info.h"
#include "gromacs/utility/basenetwork.h"
namespace test
{
+const char *codePathToString(CodePath codePath)
+{
+ switch (codePath)
+ {
+ case CodePath::CPU:
+ return "CPU";
+ case CodePath::CUDA:
+ return "CUDA";
+ default:
+ GMX_THROW(NotImplementedError("This CodePath should support codePathToString"));
+ }
+ return "";
+}
+
/* Implements the "construct on first use" idiom to avoid any static
* initialization order fiasco.
*
//! Simple hardware initialization
static gmx_hw_info_t *hardwareInit()
{
- LoggerBuilder builder;
- LoggerOwner logOwner(builder.build());
- MDLogger log(logOwner.logger());
PhysicalNodeCommunicator physicalNodeComm(MPI_COMM_WORLD, gmx_physicalnode_id_hash());
- return gmx_detect_hardware(log, physicalNodeComm);
+ return gmx_detect_hardware(MDLogger {}, physicalNodeComm);
}
void PmeTestEnvironment::SetUp()
{
- TestHardwareContext emptyContext("", nullptr);
- hardwareContextsByMode_[CodePath::CPU].push_back(emptyContext);
+ hardwareContexts_.emplace_back(TestHardwareContext(CodePath::CPU, "", nullptr));
hardwareInfo_ = hardwareInit();
-
+ if (!pme_gpu_supports_build(nullptr))
+ {
+ // PME can only run on the CPU, so don't make any more test contexts.
+ return;
+ }
// Constructing contexts for all compatible GPUs - will be empty on non-GPU builds
- TestHardwareContexts gpuContexts;
for (int gpuIndex : getCompatibleGpus(hardwareInfo_->gpu_info))
{
char stmp[200] = {};
get_gpu_device_info_string(stmp, hardwareInfo_->gpu_info, gpuIndex);
std::string description = "(GPU " + std::string(stmp) + ") ";
- gpuContexts.emplace_back(TestHardwareContext(description.c_str(), getDeviceInfo(hardwareInfo_->gpu_info, gpuIndex)));
+ // TODO should this be CodePath::GPU?
+ hardwareContexts_.emplace_back(TestHardwareContext(CodePath::CUDA, description.c_str(), getDeviceInfo(hardwareInfo_->gpu_info, gpuIndex)));
}
-#if GMX_GPU == GMX_GPU_CUDA
- hardwareContextsByMode_[CodePath::CUDA] = gpuContexts;
-#endif
}
void PmeTestEnvironment::TearDown()
/*
* This file is part of the GROMACS molecular simulation package.
*
- * Copyright (c) 2017, by the GROMACS development team, led by
+ * Copyright (c) 2017,2018, 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.
* \ingroup module_ewald
*/
-#include <list>
#include <map>
+#include <vector>
#include <gtest/gtest.h>
CUDA
};
+//! Return a string useful for human-readable messages describing a \c codePath.
+const char *codePathToString(CodePath codePath);
+
/*! \internal \brief
* A structure to describe a hardware context - an abstraction over
* gmx_device_info_t with a human-readable string.
- * TODO: currently this does not know which CodePath it belongs too.
- * It probably should! That would save us one loop in all the PME tests.
*/
struct TestHardwareContext
{
+ //! Hardware path for the code being tested.
+ CodePath codePath_;
//! Readable description
std::string description_;
//! Device information pointer
gmx_device_info_t *deviceInfo_;
public:
+ //! Retuns the code path for this context.
+ CodePath getCodePath() const { return codePath_; }
//! Returns a human-readable context description line
- std::string getDescription() const{return description_; }
-//! Returns the device info pointer
+ std::string getDescription() const{return description_; }
+ //! Returns the device info pointer
gmx_device_info_t *getDeviceInfo() const{return deviceInfo_; }
//! Constructs the context
- TestHardwareContext(const char *description, gmx_device_info_t *deviceInfo) : description_(description), deviceInfo_(deviceInfo){}
+ TestHardwareContext(CodePath codePath, const char *description, gmx_device_info_t *deviceInfo) :
+ codePath_(codePath), description_(description), deviceInfo_(deviceInfo){}
};
-//! A list of hardware contexts
-typedef std::list<TestHardwareContext> TestHardwareContexts;
+//! A container of hardware contexts
+typedef std::vector<TestHardwareContext> TestHardwareContexts;
/*! \internal \brief
* This class performs one-time test initialization (enumerating the hardware)
{
private:
//! General hardware info
- gmx_hw_info_t *hardwareInfo_;
+ gmx_hw_info_t *hardwareInfo_;
//! Storage of hardware contexts
- std::map<CodePath, TestHardwareContexts> hardwareContextsByMode_;
+ TestHardwareContexts hardwareContexts_;
public:
//! This is called by GTest framework once to query the hardware
virtual void SetUp();
//! This is called by GTest framework once to clean up
virtual void TearDown();
- //! Get available hardware contexts for given code path
- const TestHardwareContexts &getHardwareContexts(CodePath mode) const {return hardwareContextsByMode_.at(mode); }
+ //! Get available hardware contexts.
+ const TestHardwareContexts &getHardwareContexts() const {return hardwareContexts_; }
};
//! Get the test environment
inputrec->cutoff_scheme == ecutsVERLET,
gpuAccelerationOfNonbondedIsUseful(mdlog, inputrec, GMX_THREAD_MPI),
hw_opt.nthreads_tmpi);
- auto inputSystemHasPme = EEL_PME(inputrec->coulombtype) || EVDW_PME(inputrec->vdwtype);
- auto canUseGpuForPme = inputSystemHasPme && pme_gpu_supports_input(inputrec, nullptr);
+ auto canUseGpuForPme = pme_gpu_supports_build(nullptr) && pme_gpu_supports_input(inputrec, nullptr);
useGpuForPme = decideWhetherToUseGpusForPmeWithThreadMpi
(useGpuForNonbonded, pmeTarget, gpuIdsToUse, userGpuTaskAssignment,
canUseGpuForPme, hw_opt.nthreads_tmpi, domdecOptions.numPmeRanks);
emulateGpuNonbonded, inputrec->cutoff_scheme == ecutsVERLET,
gpuAccelerationOfNonbondedIsUseful(mdlog, inputrec, !GMX_THREAD_MPI),
gpusWereDetected);
- auto inputSystemHasPme = EEL_PME(inputrec->coulombtype) || EVDW_PME(inputrec->vdwtype);
- auto canUseGpuForPme = inputSystemHasPme && pme_gpu_supports_input(inputrec, nullptr);
+ auto canUseGpuForPme = pme_gpu_supports_build(nullptr) && pme_gpu_supports_input(inputrec, nullptr);
useGpuForPme = decideWhetherToUseGpusForPme(useGpuForNonbonded, pmeTarget, userGpuTaskAssignment,
canUseGpuForPme, cr->nnodes, domdecOptions.numPmeRanks,
gpusWereDetected);
if (pmeOnGpu)
{
GMX_RELEASE_ASSERT((EEL_PME(inputrec->coulombtype) || EVDW_PME(inputrec->vdwtype)) &&
- pme_gpu_supports_input(inputrec, nullptr),
+ pme_gpu_supports_build(nullptr) && pme_gpu_supports_input(inputrec, nullptr),
"PME can't be on GPUs unless we are using PME");
// PME on GPUs supports a single PME rank with PP running on the same or few other ranks.