Clean up PME testing
authorMark Abraham <mark.j.abraham@gmail.com>
Wed, 16 May 2018 19:28:42 +0000 (21:28 +0200)
committerAleksei Iupinov <a.yupinov@gmail.com>
Wed, 23 May 2018 21:38:31 +0000 (23:38 +0200)
Resolves a TODO to simplify things by building hardware contexts that
are already aware of the code path they can test.

Also stops doing unecessary copying of TestHardwareContext objects.

Setting up the hardware contexts for PME testing duplicated the
knowledge that a CUDA build is required in order for testing PME on
GPUs to be relevant. So this patch separates the checking for whether
the build can support PME on a GPU from whether the user's input is
able to run on a GPU. Extracted a common function from related
code. Used less preprocessing. Noted an additional TODO to start using
the context these messages provide.

Change-Id: Ic2dda686168f69ab6ea845f841f774a84ddd9d0d

src/gromacs/ewald/pme.cpp
src/gromacs/ewald/pme.h
src/gromacs/ewald/tests/pmegathertest.cpp
src/gromacs/ewald/tests/pmesolvetest.cpp
src/gromacs/ewald/tests/pmesplinespreadtest.cpp
src/gromacs/ewald/tests/pmetestcommon.cpp
src/gromacs/ewald/tests/testhardwarecontexts.cpp
src/gromacs/ewald/tests/testhardwarecontexts.h
src/gromacs/mdrun/runner.cpp
src/gromacs/taskassignment/resourcedivision.cpp

index e386eba265be933f608dabaacb1ef8c49ae7858d..c18fa0881a396e968ea80bf08e8efb9bc84e90e4 100644 (file)
 #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;
@@ -142,16 +174,6 @@ bool pme_gpu_supports_input(const t_inputrec *ir, std::string *error)
     {
         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");
@@ -160,15 +182,7 @@ bool pme_gpu_supports_input(const t_inputrec *ir, std::string *error)
     {
         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
@@ -199,25 +213,16 @@ static bool pme_gpu_check_restrictions(const gmx_pme_t *pme, std::string *error)
     {
         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)
index 84f121959f1c4862c15a7bbab73c20bf2a082bcb..88ffa8d8ab812b1deb1c7804d6f4084e41cc166e 100644 (file)
@@ -234,13 +234,24 @@ void gmx_pme_reinit_atoms(const gmx_pme_t *pme, const int nAtoms, const real *ch
 
 /* 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.
  */
index f32d01820d72e910973399f2f3d2f72e17fa4f65..f89e8e4eaf03825267bcb2ecdfecc9606251c94e 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * 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.
@@ -386,58 +386,53 @@ class PmeGatherTest : public ::testing::TestWithParam<GatherInputParameters>
             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");
             }
         }
 };
index cf0388cef2d0618d8750df7ffdf8fc1335b42a80..a86b356ede3928f85e5b3d83afe8463d3f1b7251 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * 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.
@@ -109,132 +109,127 @@ class PmeSolveTest : public ::testing::TestWithParam<SolveInputParameters>
                     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());
                                 }
                             }
                         }
index 0e93ced28ed8b664e7e5de0ef67fb3d30bccf61c..71988a6ef79508e8734907d5a4398ccc3a7101c8 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * 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.
@@ -107,9 +107,6 @@ class PmeSplineAndSpreadTest : public ::testing::TestWithParam<SplineAndSpreadIn
 
             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"}};
@@ -121,118 +118,115 @@ class PmeSplineAndSpreadTest : public ::testing::TestWithParam<SplineAndSpreadIn
             // 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());
                         }
                     }
                 }
index d47c1e9d35c12a92cc94b0b95df35bbc1bc37d0d..781bf53a61a18460a193a7bb1f552f676e4038e9 100644 (file)
@@ -79,7 +79,8 @@ bool pmeSupportsInputForMode(const t_inputrec *inputRec, CodePath mode)
             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:
index b62da2b215c88904e7deeae3580e0167b56f7b87..a4f0c7cfce2294c1579f646aa90ca06d042bc70f 100644 (file)
@@ -44,8 +44,7 @@
 
 #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"
@@ -58,6 +57,20 @@ namespace gmx
 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.
  *
@@ -87,32 +100,29 @@ void callAddGlobalTestEnvironment()
 //! 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()
index 82128b75df1f18b229cb3a4167daa3867c06415f..09ab2d1c358d6707aebe2bbc5198802c96f935f7 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * 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.
@@ -43,8 +43,8 @@
  * \ingroup module_ewald
  */
 
-#include <list>
 #include <map>
+#include <vector>
 
 #include <gtest/gtest.h>
 
@@ -62,30 +62,36 @@ enum class CodePath
     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)
@@ -95,17 +101,17 @@ class PmeTestEnvironment : public ::testing::Environment
 {
     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
index 729a293626a517db580cce17db6b9ace28a40522..96a179990ed71754d2753694dd191b757199971d 100644 (file)
@@ -563,8 +563,7 @@ int Mdrunner::mdrunner()
                     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);
@@ -626,8 +625,7 @@ int Mdrunner::mdrunner()
                                                                 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);
index e2805f1ff438c7ed2753aa02e2dfa0f4e68f2082..2b7347b92713f0879a7fa0bee656bdb3bccf7706 100644 (file)
@@ -361,7 +361,7 @@ int get_nthreads_mpi(const gmx_hw_info_t    *hwinfo,
     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.