Generalize constraints on MPI rank counts for tests
[alexxy/gromacs.git] / src / programs / mdrun / tests / pmetest.cpp
index 53f65ffab85e89661c1dad02c641b1245ff3e350..7dadd5d0ea9ca03032a09491bfe0d9801f661966 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2016,2017,2018, by the GROMACS development team, led by
+ * Copyright (c) 2016,2017,2018,2019,2020,2021, 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.
  */
 #include "gmxpre.h"
 
-#include "config.h"
-
 #include <map>
 #include <string>
 #include <vector>
 
 #include <gtest/gtest-spi.h>
 
-#include "gromacs/gpu_utils/gpu_utils.h"
-#include "gromacs/hardware/gpu_hw_info.h"
+#include "gromacs/ewald/pme.h"
+#include "gromacs/hardware/detecthardware.h"
+#include "gromacs/hardware/device_management.h"
+#include "gromacs/hardware/hw_info.h"
+#include "gromacs/trajectory/energyframe.h"
+#include "gromacs/utility/basenetwork.h"
 #include "gromacs/utility/cstringutil.h"
 #include "gromacs/utility/gmxmpi.h"
+#include "gromacs/utility/physicalnodecommunicator.h"
 #include "gromacs/utility/stringutil.h"
 
 #include "testutils/mpitest.h"
@@ -80,46 +83,17 @@ namespace
  * \todo Consider also using GpuTest class. */
 class PmeTest : public MdrunTestFixture
 {
-    public:
-        //! Before any test is run, work out whether any compatible GPUs exist.
-        static void SetUpTestCase();
-        //! Store whether any compatible GPUs exist.
-        static bool s_hasCompatibleCudaGpus;
+public:
+    //! Convenience typedef
+    using RunModesList = std::map<std::string, std::vector<const char*>>;
+    //! Runs the test with the given inputs
+    void runTest(const RunModesList& runModes);
 };
 
-bool PmeTest::s_hasCompatibleCudaGpus = false;
-
-void PmeTest::SetUpTestCase()
-{
-    gmx_gpu_info_t gpuInfo {};
-    // It would be nicer to do this detection once and have mdrun
-    // re-use it, but this is OK. Note that this also caters for when
-    // there is no GPU support in the build.
-    //
-    // TODO report any error messages gracefully.
-    if (GMX_GPU == GMX_GPU_CUDA &&
-        canDetectGpus(nullptr))
-    {
-        findGpus(&gpuInfo);
-        s_hasCompatibleCudaGpus = (gpuInfo.n_dev_compatible > 0);
-    }
-    free_gpu_info(&gpuInfo);
-}
-
-TEST_F(PmeTest, ReproducesEnergies)
+void PmeTest::runTest(const RunModesList& runModes)
 {
-    const int   nsteps     = 20;
-    std::string theMdpFile = formatString("coulombtype     = PME\n"
-                                          "nstcalcenergy   = 1\n"
-                                          "nstenergy       = 1\n"
-                                          "pme-order       = 4\n"
-                                          "nsteps          = %d\n",
-                                          nsteps);
-
-    runner_.useStringAsMdpFile(theMdpFile);
-
     const std::string inputFile = "spc-and-methanol";
-    runner_.useTopGroAndNdxFromDatabase(inputFile.c_str());
+    runner_.useTopGroAndNdxFromDatabase(inputFile);
 
     // With single rank we can and will always test PP+PME as part of mdrun-test.
     // With multiple ranks we can additionally test a single PME-only rank within mdrun-mpi-test.
@@ -128,17 +102,6 @@ TEST_F(PmeTest, ReproducesEnergies)
 
     EXPECT_EQ(0, runner_.callGrompp());
 
-    //TODO test all proper/improper combinations in more thorough way?
-    std::map < std::string, std::vector < const char *>> runModes;
-    runModes["PmeOnCpu"]         = {"-pme", "cpu"};
-    runModes["PmeAuto"]          = {"-pme", "auto"};
-    runModes["PmeOnGpuFftOnCpu"] = {"-pme", "gpu", "-pmefft", "cpu"};
-    runModes["PmeOnGpuFftOnGpu"] = {"-pme", "gpu", "-pmefft", "gpu"};
-    runModes["PmeOnGpuFftAuto"]  = {"-pme", "gpu", "-pmefft", "auto"};
-    // same manual modes but marked for PME tuning
-    runModes["PmeOnCpuTune"]         = {"-pme", "cpu"};
-    runModes["PmeOnGpuFftOnCpuTune"] = {"-pme", "gpu", "-pmefft", "cpu"};
-    runModes["PmeOnGpuFftOnGpuTune"] = {"-pme", "gpu", "-pmefft", "gpu"};
     TestReferenceData    refData;
     TestReferenceChecker rootChecker(refData.rootChecker());
     const bool           thisRankChecks = (gmx_node_rank() == 0);
@@ -146,22 +109,37 @@ TEST_F(PmeTest, ReproducesEnergies)
     {
         EXPECT_NONFATAL_FAILURE(rootChecker.checkUnusedEntries(), ""); // skip checks on other ranks
     }
-    for (const auto &mode : runModes)
+
+    auto hardwareInfo_ =
+            gmx_detect_hardware(PhysicalNodeCommunicator(MPI_COMM_WORLD, gmx_physicalnode_id_hash()));
+
+    for (const auto& mode : runModes)
     {
+        SCOPED_TRACE("mdrun " + joinStrings(mode.second, " "));
         auto modeTargetsGpus = (mode.first.find("Gpu") != std::string::npos);
-        if (modeTargetsGpus && !s_hasCompatibleCudaGpus)
+        if (modeTargetsGpus && getCompatibleDevices(hardwareInfo_->deviceInfoList).empty())
         {
             // This run mode will cause a fatal error from mdrun when
             // it can't find GPUs, which is not something we're trying
             // to test here.
             continue;
         }
+        auto modeTargetsPmeOnGpus = (mode.first.find("PmeOnGpu") != std::string::npos);
+        if (modeTargetsPmeOnGpus
+            && !(pme_gpu_supports_build(nullptr) && pme_gpu_supports_hardware(*hardwareInfo_, nullptr)))
+        {
+            // This run mode will cause a fatal error from mdrun when
+            // it finds an unsuitable device, which is not something
+            // we're trying to test here.
+            continue;
+        }
 
-        runner_.edrFileName_ = fileManager_.getTemporaryFilePath(inputFile + "_" + mode.first + ".edr");
+        runner_.edrFileName_ =
+                fileManager_.getTemporaryFilePath(inputFile + "_" + mode.first + ".edr");
 
         CommandLine commandLine(mode.second);
 
-        const bool  usePmeTuning = (mode.first.find("Tune") != std::string::npos);
+        const bool usePmeTuning = (mode.first.find("Tune") != std::string::npos);
         if (usePmeTuning)
         {
             commandLine.append("-tunepme");
@@ -175,38 +153,121 @@ TEST_F(PmeTest, ReproducesEnergies)
         {
             commandLine.addOption("-npme", 1);
         }
+
         ASSERT_EQ(0, runner_.callMdrun(commandLine));
 
         if (thisRankChecks)
         {
-            auto energyReader      = openEnergyFileToReadFields(runner_.edrFileName_, {"Coul. recip.", "Total Energy", "Kinetic En."});
+            auto energyReader = openEnergyFileToReadTerms(
+                    runner_.edrFileName_, { "Coul. recip.", "Total Energy", "Kinetic En." });
             auto conservedChecker  = rootChecker.checkCompound("Energy", "Conserved");
             auto reciprocalChecker = rootChecker.checkCompound("Energy", "Reciprocal");
-            for (int i = 0; i <= nsteps; i++)
+            bool firstIteration    = true;
+            while (energyReader->readNextFrame())
             {
-                EnergyFrame frame            = energyReader->frame();
-                std::string stepNum          = gmx::formatString("%d", i);
-                const real  conservedEnergy  = frame.at("Total Energy");
-                const real  reciprocalEnergy = frame.at("Coul. recip.");
-                if (i == 0)
+                const EnergyFrame& frame            = energyReader->frame();
+                const std::string  stepName         = frame.frameName();
+                const real         conservedEnergy  = frame.at("Total Energy");
+                const real         reciprocalEnergy = frame.at("Coul. recip.");
+                if (firstIteration)
                 {
                     // use first step values as references for tolerance
                     const real startingKineticEnergy = frame.at("Kinetic En.");
-                    const auto conservedTolerance    = relativeToleranceAsFloatingPoint(startingKineticEnergy, 2e-5);
-                    const auto reciprocalTolerance   = relativeToleranceAsFloatingPoint(reciprocalEnergy, 3e-5);
+                    const auto conservedTolerance =
+                            relativeToleranceAsFloatingPoint(startingKineticEnergy, 2e-5);
+                    const auto reciprocalTolerance =
+                            relativeToleranceAsFloatingPoint(reciprocalEnergy, 3e-5);
                     reciprocalChecker.setDefaultTolerance(reciprocalTolerance);
                     conservedChecker.setDefaultTolerance(conservedTolerance);
+                    firstIteration = false;
                 }
-                conservedChecker.checkReal(conservedEnergy, stepNum.c_str());
+                conservedChecker.checkReal(conservedEnergy, stepName.c_str());
                 if (!usePmeTuning) // with PME tuning come differing grids and differing reciprocal energy
                 {
-                    reciprocalChecker.checkReal(reciprocalEnergy, stepNum.c_str());
+                    reciprocalChecker.checkReal(reciprocalEnergy, stepName.c_str());
                 }
             }
         }
     }
 }
 
+TEST_F(PmeTest, ReproducesEnergies)
+{
+    const int         nsteps     = 20;
+    const std::string theMdpFile = formatString(
+            "coulombtype     = PME\n"
+            "nstcalcenergy   = 1\n"
+            "nstenergy       = 1\n"
+            "pme-order       = 4\n"
+            "nsteps          = %d\n",
+            nsteps);
+
+    runner_.useStringAsMdpFile(theMdpFile);
+
+    // TODO test all proper/improper combinations in more thorough way?
+    RunModesList runModes;
+    runModes["PmeOnCpu"]         = { "-pme", "cpu" };
+    runModes["PmeAuto"]          = { "-pme", "auto" };
+    runModes["PmeOnGpuFftOnCpu"] = { "-pme", "gpu", "-pmefft", "cpu" };
+    runModes["PmeOnGpuFftOnGpu"] = { "-pme", "gpu", "-pmefft", "gpu" };
+    runModes["PmeOnGpuFftAuto"]  = { "-pme", "gpu", "-pmefft", "auto" };
+    // same manual modes but marked for PME tuning
+    runModes["PmeOnCpuTune"]         = { "-pme", "cpu" };
+    runModes["PmeOnGpuFftOnCpuTune"] = { "-pme", "gpu", "-pmefft", "cpu" };
+    runModes["PmeOnGpuFftOnGpuTune"] = { "-pme", "gpu", "-pmefft", "gpu" };
+
+    runTest(runModes);
 }
+
+TEST_F(PmeTest, ScalesTheBox)
+{
+    const int         nsteps     = 0;
+    const std::string theMdpFile = formatString(
+            "coulombtype     = PME\n"
+            "nstcalcenergy   = 1\n"
+            "nstenergy       = 1\n"
+            "pme-order       = 4\n"
+            "pbc             = xyz\n"
+            "nsteps          = %d\n",
+            nsteps);
+
+    runner_.useStringAsMdpFile(theMdpFile);
+
+    RunModesList runModes;
+    runModes["PmeOnCpu"]         = { "-pme", "cpu" };
+    runModes["PmeOnGpuFftOnCpu"] = { "-pme", "gpu", "-pmefft", "cpu" };
+    runModes["PmeOnGpuFftOnGpu"] = { "-pme", "gpu", "-pmefft", "gpu" };
+
+    runTest(runModes);
 }
+
+TEST_F(PmeTest, ScalesTheBoxWithWalls)
+{
+    const int         nsteps     = 0;
+    const std::string theMdpFile = formatString(
+            "coulombtype     = PME\n"
+            "nstcalcenergy   = 1\n"
+            "nstenergy       = 1\n"
+            "pme-order       = 4\n"
+            "pbc             = xy\n"
+            "nwall           = 2\n"
+            "ewald-geometry  = 3dc\n"
+            "wall_atomtype   = CMet H\n"
+            "wall_density    = 9 9.0\n"
+            "wall-ewald-zfac = 5\n"
+            "nsteps          = %d\n",
+            nsteps);
+
+    runner_.useStringAsMdpFile(theMdpFile);
+
+    RunModesList runModes;
+    runModes["PmeOnCpu"]         = { "-pme", "cpu" };
+    runModes["PmeOnGpuFftOnCpu"] = { "-pme", "gpu", "-pmefft", "cpu" };
+    runModes["PmeOnGpuFftOnGpu"] = { "-pme", "gpu", "-pmefft", "gpu" };
+
+    runTest(runModes);
 }
+
+} // namespace
+} // namespace test
+} // namespace gmx