Set up event consumption counter in DomDecMpiTests
[alexxy/gromacs.git] / src / gromacs / domdec / tests / haloexchange_mpi.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2020,2021, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief Tests for the halo exchange
37  *
38  *  The test sets up the rank topology and performs a coordinate halo
39  *  exchange (for both CPU and GPU codepaths) for several 1D and 2D
40  *  pulse configirations. Each pulse involves a few non-contiguous
41  *  indices. The sending rank, atom number and spatial 3D index are
42  *  encoded in the x values, to allow correctness checking following
43  *  the halo exchange.
44  *
45  * \todo Add 3D case
46  *
47  * \author Alan Gray <alang@nvidia.com>
48  * \ingroup module_domdec
49  */
50
51 #include "gmxpre.h"
52
53 #include "config.h"
54
55 #include <array>
56 #include <numeric>
57 #include <vector>
58
59 #include <gtest/gtest.h>
60
61 #include "gromacs/domdec/atomdistribution.h"
62 #include "gromacs/domdec/domdec_internal.h"
63 #include "gromacs/domdec/gpuhaloexchange.h"
64 #if GMX_GPU_CUDA
65 #    include "gromacs/gpu_utils/device_stream.h"
66 #    include "gromacs/gpu_utils/devicebuffer.h"
67 #endif
68 #include "gromacs/gpu_utils/gpueventsynchronizer.h"
69 #include "gromacs/gpu_utils/hostallocator.h"
70 #include "gromacs/mdtypes/inputrec.h"
71
72 #include "testutils/mpitest.h"
73 #include "testutils/test_hardware_environment.h"
74
75 namespace gmx
76 {
77 namespace test
78 {
79 namespace
80 {
81
82 /*! \brief Get encoded numerical value for sending rank, atom number and spatial 3D index
83  *
84  * \param [in] sendRank         MPI rank of sender
85  * \param [in] atomNumber       Atom number
86  * \param [in] spatial3dIndex   Spatial 3D Index
87  *
88  * \returns                     Encoded value
89  */
90 float encodedValue(const int sendRank, const int atomNumber, const int spatial3dIndex)
91 {
92     return sendRank * 1000 + atomNumber * 100 + spatial3dIndex;
93 }
94
95 /*! \brief Initialize halo array
96  *
97  * \param [in] x              Atom coordinate data array
98  * \param [in] numHomeAtoms   Number of home atoms
99  * \param [in] numAtomsTotal  Total number of atoms, including halo
100  */
101 void initHaloData(RVec* x, const int numHomeAtoms, const int numAtomsTotal)
102 {
103     int rank;
104     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
105
106     for (int i = 0; i < numAtomsTotal; i++)
107     {
108         for (int j = 0; j < DIM; j++)
109         {
110             x[i][j] = i < numHomeAtoms ? encodedValue(rank, i, j) : -1;
111         }
112     }
113 }
114
115 /*! \brief Perform GPU halo exchange, including required setup and data transfers
116  *
117  * \param [in] dd             Domain decomposition object
118  * \param [in] box            Box matrix
119  * \param [in] h_x            Atom coordinate data array on host
120  * \param [in] numAtomsTotal  Total number of atoms, including halo
121  */
122 void gpuHalo(gmx_domdec_t* dd, matrix box, HostVector<RVec>* h_x, int numAtomsTotal)
123 {
124 #if (GMX_GPU_CUDA && GMX_THREAD_MPI)
125     // pin memory if possible
126     changePinningPolicy(h_x, PinningPolicy::PinnedIfSupported);
127     // Set up GPU hardware environment and assign this MPI rank to a device
128     int rank;
129     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
130     int         numDevices = getTestHardwareEnvironment()->getTestDeviceList().size();
131     const auto& testDevice = getTestHardwareEnvironment()->getTestDeviceList()[rank % numDevices];
132     const auto& deviceContext = testDevice->deviceContext();
133     setActiveDevice(testDevice->deviceInfo());
134     DeviceStream deviceStream(deviceContext, DeviceStreamPriority::Normal, false);
135
136     // Set up GPU buffer and copy input data from host
137     DeviceBuffer<RVec> d_x;
138     int                d_x_size       = -1;
139     int                d_x_size_alloc = -1;
140     reallocateDeviceBuffer(&d_x, numAtomsTotal, &d_x_size, &d_x_size_alloc, deviceContext);
141
142     copyToDeviceBuffer(&d_x, h_x->data(), 0, numAtomsTotal, deviceStream, GpuApiCallBehavior::Sync, nullptr);
143
144     const int numPulses = std::accumulate(
145             dd->comm->cd.begin(), dd->comm->cd.end(), 0, [](const int a, const auto& b) {
146                 return a + b.numPulses();
147             });
148     const int numExtraConsumptions = GMX_THREAD_MPI ? 1 : 0;
149     // Will be consumed once for each pulse, and, with tMPI, once more for dim=0,pulse=0 case
150     GpuEventSynchronizer coordinatesReadyOnDeviceEvent(numPulses + numExtraConsumptions,
151                                                        numPulses + numExtraConsumptions);
152     coordinatesReadyOnDeviceEvent.markEvent(deviceStream);
153
154     std::array<std::vector<GpuHaloExchange>, DIM> gpuHaloExchange;
155
156     // Create halo exchange objects
157     for (int d = 0; d < dd->ndim; d++)
158     {
159         for (int pulse = 0; pulse < dd->comm->cd[d].numPulses(); pulse++)
160         {
161             gpuHaloExchange[d].push_back(
162                     GpuHaloExchange(dd, d, MPI_COMM_WORLD, deviceContext, pulse, nullptr));
163         }
164     }
165
166     // Perform GPU halo exchange
167     for (int d = 0; d < dd->ndim; d++)
168     {
169         for (int pulse = 0; pulse < dd->comm->cd[d].numPulses(); pulse++)
170         {
171             gpuHaloExchange[d][pulse].reinitHalo(d_x, nullptr);
172             gpuHaloExchange[d][pulse].communicateHaloCoordinates(box, &coordinatesReadyOnDeviceEvent);
173         }
174     }
175     // Barrier is needed to avoid other threads using events after its owner has exited and destroyed the context.
176     MPI_Barrier(MPI_COMM_WORLD);
177
178     deviceStream.synchronize();
179
180     // Copy results back to host
181     copyFromDeviceBuffer(
182             h_x->data(), &d_x, 0, numAtomsTotal, deviceStream, GpuApiCallBehavior::Sync, nullptr);
183
184     freeDeviceBuffer(d_x);
185 #else
186     GMX_UNUSED_VALUE(dd);
187     GMX_UNUSED_VALUE(box);
188     GMX_UNUSED_VALUE(h_x);
189     GMX_UNUSED_VALUE(numAtomsTotal);
190 #endif
191 }
192
193 /*! \brief Define 1D rank topology with 4 MPI tasks
194  *
195  * \param [in] dd  Domain decomposition object
196  */
197 void define1dRankTopology(gmx_domdec_t* dd)
198 {
199     int rank;
200     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
201
202     const int numRanks = getNumberOfTestMpiRanks();
203     dd->neighbor[0][0] = (rank + 1) % numRanks;
204     dd->neighbor[0][1] = (rank == 0) ? (numRanks - 1) : rank - 1;
205 }
206
207 /*! \brief Define 2D rank topology with 4 MPI tasks
208  *
209  *    -----
210  *   | 2 3 |
211  *   | 0 1 |
212  *    -----
213  *
214  * \param [in] dd  Domain decomposition object
215  */
216 void define2dRankTopology(gmx_domdec_t* dd)
217 {
218
219     int rank;
220     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
221
222     switch (rank)
223     {
224         case 0:
225             dd->neighbor[0][0] = 1;
226             dd->neighbor[0][1] = 1;
227             dd->neighbor[1][0] = 2;
228             dd->neighbor[1][1] = 2;
229             break;
230         case 1:
231             dd->neighbor[0][0] = 0;
232             dd->neighbor[0][1] = 0;
233             dd->neighbor[1][0] = 3;
234             dd->neighbor[1][1] = 3;
235             break;
236         case 2:
237             dd->neighbor[0][0] = 3;
238             dd->neighbor[0][1] = 3;
239             dd->neighbor[1][0] = 0;
240             dd->neighbor[1][1] = 0;
241             break;
242         case 3:
243             dd->neighbor[0][0] = 2;
244             dd->neighbor[0][1] = 2;
245             dd->neighbor[1][0] = 1;
246             dd->neighbor[1][1] = 1;
247             break;
248     }
249 }
250
251 /*! \brief Define a 1D halo with 1 pulses
252  *
253  * \param [in] dd      Domain decomposition object
254  * \param [in] indvec  Vector of index vectors
255  */
256 void define1dHaloWith1Pulse(gmx_domdec_t* dd, std::vector<gmx_domdec_ind_t>* indvec)
257 {
258
259     int rank;
260     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
261
262     std::vector<int> indexvec;
263     gmx_domdec_ind_t ind;
264
265     dd->ndim     = 1;
266     int nzone    = 1;
267     int dimIndex = 0;
268
269     // Set up indices involved in halo
270     indexvec.clear();
271     indvec->clear();
272
273     dd->comm->cd[dimIndex].receiveInPlace = true;
274     dd->dim[dimIndex]                     = 0;
275     dd->ci[dimIndex]                      = rank;
276
277     // First pulse involves (arbitrary) indices 1 and 3
278     indexvec.push_back(1);
279     indexvec.push_back(3);
280
281     ind.index            = indexvec;
282     ind.nsend[nzone + 1] = 2;
283     ind.nrecv[nzone + 1] = 2;
284     indvec->push_back(ind);
285
286     dd->comm->cd[dimIndex].ind = *indvec;
287 }
288
289 /*! \brief Define a 1D halo with 2 pulses
290  *
291  * \param [in] dd      Domain decomposition object
292  * \param [in] indvec  Vector of index vectors
293  */
294 void define1dHaloWith2Pulses(gmx_domdec_t* dd, std::vector<gmx_domdec_ind_t>* indvec)
295 {
296
297     int rank;
298     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
299
300     std::vector<int> indexvec;
301     gmx_domdec_ind_t ind;
302
303     dd->ndim     = 1;
304     int nzone    = 1;
305     int dimIndex = 0;
306
307     // Set up indices involved in halo
308     indexvec.clear();
309     indvec->clear();
310
311     dd->comm->cd[dimIndex].receiveInPlace = true;
312     dd->dim[dimIndex]                     = 0;
313     dd->ci[dimIndex]                      = rank;
314
315     // First pulse involves (arbitrary) indices 1 and 3
316     indexvec.push_back(1);
317     indexvec.push_back(3);
318
319     ind.index            = indexvec;
320     ind.nsend[nzone + 1] = 2;
321     ind.nrecv[nzone + 1] = 2;
322     indvec->push_back(ind);
323
324     // Add another pulse with (arbitrary) indices 4,5,7
325     indexvec.clear();
326
327     indexvec.push_back(4);
328     indexvec.push_back(5);
329     indexvec.push_back(7);
330
331     ind.index            = indexvec;
332     ind.nsend[nzone + 1] = 3;
333     ind.nrecv[nzone + 1] = 3;
334     indvec->push_back(ind);
335
336     dd->comm->cd[dimIndex].ind = *indvec;
337 }
338
339 /*! \brief Define a 2D halo with 1 pulse in each dimension
340  *
341  * \param [in] dd      Domain decomposition object
342  * \param [in] indvec  Vector of index vectors
343  */
344 void define2dHaloWith1PulseInEachDim(gmx_domdec_t* dd, std::vector<gmx_domdec_ind_t>* indvec)
345 {
346
347     int rank;
348     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
349
350     std::vector<int> indexvec;
351     gmx_domdec_ind_t ind;
352
353     dd->ndim  = 2;
354     int nzone = 1;
355     for (int dimIndex = 0; dimIndex < dd->ndim; dimIndex++)
356     {
357
358         // Set up indices involved in halo
359         indexvec.clear();
360         indvec->clear();
361
362         dd->comm->cd[dimIndex].receiveInPlace = true;
363         dd->dim[dimIndex]                     = 0;
364         dd->ci[dimIndex]                      = rank;
365
366         // Single pulse involving (arbitrary) indices 1 and 3
367         indexvec.push_back(1);
368         indexvec.push_back(3);
369
370         ind.index            = indexvec;
371         ind.nsend[nzone + 1] = 2;
372         ind.nrecv[nzone + 1] = 2;
373         indvec->push_back(ind);
374
375         dd->comm->cd[dimIndex].ind = *indvec;
376
377         nzone += nzone;
378     }
379 }
380
381 /*! \brief Define a 2D halo with 2 pulses in the first dimension
382  *
383  * \param [in] dd      Domain decomposition object
384  * \param [in] indvec  Vector of index vectors
385  */
386 void define2dHaloWith2PulsesInDim1(gmx_domdec_t* dd, std::vector<gmx_domdec_ind_t>* indvec)
387 {
388
389     int rank;
390     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
391
392     std::vector<int> indexvec;
393     gmx_domdec_ind_t ind;
394
395     dd->ndim  = 2;
396     int nzone = 1;
397     for (int dimIndex = 0; dimIndex < dd->ndim; dimIndex++)
398     {
399
400         // Set up indices involved in halo
401         indexvec.clear();
402         indvec->clear();
403
404         dd->comm->cd[dimIndex].receiveInPlace = true;
405         dd->dim[dimIndex]                     = 0;
406         dd->ci[dimIndex]                      = rank;
407
408         // First pulse involves (arbitrary) indices 1 and 3
409         indexvec.push_back(1);
410         indexvec.push_back(3);
411
412         ind.index            = indexvec;
413         ind.nsend[nzone + 1] = 2;
414         ind.nrecv[nzone + 1] = 2;
415         indvec->push_back(ind);
416
417         if (dimIndex == 0) // Add another pulse with (arbitrary) indices 4,5,7
418         {
419             indexvec.clear();
420
421             indexvec.push_back(4);
422             indexvec.push_back(5);
423             indexvec.push_back(7);
424
425             ind.index            = indexvec;
426             ind.nsend[nzone + 1] = 3;
427             ind.nrecv[nzone + 1] = 3;
428             indvec->push_back(ind);
429         }
430
431         dd->comm->cd[dimIndex].ind = *indvec;
432
433         nzone += nzone;
434     }
435 }
436
437 /*! \brief Check results for above-defined 1D halo with 1 pulse
438  *
439  * \param [in] x             Atom coordinate data array
440  * \param [in] dd            Domain decomposition object
441  * \param [in] numHomeAtoms  Number of home atoms
442  */
443 void checkResults1dHaloWith1Pulse(const RVec* x, const gmx_domdec_t* dd, const int numHomeAtoms)
444 {
445     // Check results are expected from values encoded in x data
446     for (int j = 0; j < DIM; j++)
447     {
448         // First Pulse in first dim: atoms 1 and 3 from forward horizontal neighbour
449         EXPECT_EQ(x[numHomeAtoms][j], encodedValue(dd->neighbor[0][0], 1, j));
450         EXPECT_EQ(x[numHomeAtoms + 1][j], encodedValue(dd->neighbor[0][0], 3, j));
451     }
452 }
453
454 /*! \brief Check results for above-defined 1D halo with 2 pulses
455  *
456  * \param [in] x             Atom coordinate data array
457  * \param [in] dd            Domain decomposition object
458  * \param [in] numHomeAtoms  Number of home atoms
459  */
460 void checkResults1dHaloWith2Pulses(const RVec* x, const gmx_domdec_t* dd, const int numHomeAtoms)
461 {
462     // Check results are expected from values encoded in x data
463     for (int j = 0; j < DIM; j++)
464     {
465         // First Pulse in first dim: atoms 1 and 3 from forward horizontal neighbour
466         EXPECT_EQ(x[numHomeAtoms][j], encodedValue(dd->neighbor[0][0], 1, j));
467         EXPECT_EQ(x[numHomeAtoms + 1][j], encodedValue(dd->neighbor[0][0], 3, j));
468         // Second Pulse in first dim: atoms 4,5,7 from forward horizontal neighbour
469         EXPECT_EQ(x[numHomeAtoms + 2][j], encodedValue(dd->neighbor[0][0], 4, j));
470         EXPECT_EQ(x[numHomeAtoms + 3][j], encodedValue(dd->neighbor[0][0], 5, j));
471         EXPECT_EQ(x[numHomeAtoms + 4][j], encodedValue(dd->neighbor[0][0], 7, j));
472     }
473 }
474
475 /*! \brief Check results for above-defined 2D halo with 1 pulse in each dimension
476  *
477  * \param [in] x             Atom coordinate data array
478  * \param [in] dd            Domain decomposition object
479  * \param [in] numHomeAtoms  Number of home atoms
480  */
481 void checkResults2dHaloWith1PulseInEachDim(const RVec* x, const gmx_domdec_t* dd, const int numHomeAtoms)
482 {
483     // Check results are expected from values encoded in x data
484     for (int j = 0; j < DIM; j++)
485     {
486         // First Pulse in first dim: atoms 1 and 3 from forward horizontal neighbour
487         EXPECT_EQ(x[numHomeAtoms][j], encodedValue(dd->neighbor[0][0], 1, j));
488         EXPECT_EQ(x[numHomeAtoms + 1][j], encodedValue(dd->neighbor[0][0], 3, j));
489         // First Pulse in second dim: atoms 1 and 3 from forward vertical neighbour
490         EXPECT_EQ(x[numHomeAtoms + 2][j], encodedValue(dd->neighbor[1][0], 1, j));
491         EXPECT_EQ(x[numHomeAtoms + 3][j], encodedValue(dd->neighbor[1][0], 3, j));
492     }
493 }
494
495 /*! \brief Check results for above-defined 2D halo with 2 pulses in the first dimension
496  *
497  * \param [in] x             Atom coordinate data array
498  * \param [in] dd            Domain decomposition object
499  * \param [in] numHomeAtoms  Number of home atoms
500  */
501 void checkResults2dHaloWith2PulsesInDim1(const RVec* x, const gmx_domdec_t* dd, const int numHomeAtoms)
502 {
503     // Check results are expected from values encoded in x data
504     for (int j = 0; j < DIM; j++)
505     {
506         // First Pulse in first dim: atoms 1 and 3 from forward horizontal neighbour
507         EXPECT_EQ(x[numHomeAtoms][j], encodedValue(dd->neighbor[0][0], 1, j));
508         EXPECT_EQ(x[numHomeAtoms + 1][j], encodedValue(dd->neighbor[0][0], 3, j));
509         // Second Pulse in first dim: atoms 4,5,7 from forward horizontal neighbour
510         EXPECT_EQ(x[numHomeAtoms + 2][j], encodedValue(dd->neighbor[0][0], 4, j));
511         EXPECT_EQ(x[numHomeAtoms + 3][j], encodedValue(dd->neighbor[0][0], 5, j));
512         EXPECT_EQ(x[numHomeAtoms + 4][j], encodedValue(dd->neighbor[0][0], 7, j));
513         // First Pulse in second dim: atoms 1 and 3 from forward vertical neighbour
514         EXPECT_EQ(x[numHomeAtoms + 5][j], encodedValue(dd->neighbor[1][0], 1, j));
515         EXPECT_EQ(x[numHomeAtoms + 6][j], encodedValue(dd->neighbor[1][0], 3, j));
516     }
517 }
518
519 TEST(HaloExchangeTest, Coordinates1dHaloWith1Pulse)
520 {
521     GMX_MPI_TEST(RequireRankCount<4>);
522
523     // Set up atom data
524     const int        numHomeAtoms  = 10;
525     const int        numHaloAtoms  = 2;
526     const int        numAtomsTotal = numHomeAtoms + numHaloAtoms;
527     HostVector<RVec> h_x;
528     h_x.resize(numAtomsTotal);
529
530     initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
531
532     // Set up dd
533     t_inputrec   ir;
534     gmx_domdec_t dd(ir);
535     dd.mpi_comm_all = MPI_COMM_WORLD;
536     gmx_domdec_comm_t comm;
537     dd.comm                      = &comm;
538     dd.unitCellInfo.haveScrewPBC = false;
539
540     DDAtomRanges atomRanges;
541     atomRanges.setEnd(DDAtomRanges::Type::Home, numHomeAtoms);
542     dd.comm->atomRanges = atomRanges;
543
544     define1dRankTopology(&dd);
545
546     std::vector<gmx_domdec_ind_t> indvec;
547     define1dHaloWith1Pulse(&dd, &indvec);
548
549     // Perform halo exchange
550     matrix box = { { 0., 0., 0. } };
551     dd_move_x(&dd, box, static_cast<ArrayRef<RVec>>(h_x), nullptr);
552
553     // Check results
554     checkResults1dHaloWith1Pulse(h_x.data(), &dd, numHomeAtoms);
555
556     if (GMX_GPU_CUDA && GMX_THREAD_MPI) // repeat with GPU halo codepath
557     {
558         // early return if no devices are available.
559         if (getTestHardwareEnvironment()->getTestDeviceList().empty())
560         {
561             return;
562         }
563
564         // Re-initialize input
565         initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
566
567         // Perform GPU halo exchange
568         gpuHalo(&dd, box, &h_x, numAtomsTotal);
569
570         // Check results
571         checkResults1dHaloWith1Pulse(h_x.data(), &dd, numHomeAtoms);
572     }
573 }
574
575 TEST(HaloExchangeTest, Coordinates1dHaloWith2Pulses)
576 {
577     GMX_MPI_TEST(RequireRankCount<4>);
578
579     // Set up atom data
580     const int        numHomeAtoms  = 10;
581     const int        numHaloAtoms  = 5;
582     const int        numAtomsTotal = numHomeAtoms + numHaloAtoms;
583     HostVector<RVec> h_x;
584     h_x.resize(numAtomsTotal);
585
586     initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
587
588     // Set up dd
589     t_inputrec   ir;
590     gmx_domdec_t dd(ir);
591     dd.mpi_comm_all = MPI_COMM_WORLD;
592     gmx_domdec_comm_t comm;
593     dd.comm                      = &comm;
594     dd.unitCellInfo.haveScrewPBC = false;
595
596     DDAtomRanges atomRanges;
597     atomRanges.setEnd(DDAtomRanges::Type::Home, numHomeAtoms);
598     dd.comm->atomRanges = atomRanges;
599
600     define1dRankTopology(&dd);
601
602     std::vector<gmx_domdec_ind_t> indvec;
603     define1dHaloWith2Pulses(&dd, &indvec);
604
605     // Perform halo exchange
606     matrix box = { { 0., 0., 0. } };
607     dd_move_x(&dd, box, static_cast<ArrayRef<RVec>>(h_x), nullptr);
608
609     // Check results
610     checkResults1dHaloWith2Pulses(h_x.data(), &dd, numHomeAtoms);
611
612     if (GMX_GPU_CUDA && GMX_THREAD_MPI) // repeat with GPU halo codepath
613     {
614         // early return if no devices are available.
615         if (getTestHardwareEnvironment()->getTestDeviceList().empty())
616         {
617             return;
618         }
619
620         // Re-initialize input
621         initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
622
623         // Perform GPU halo exchange
624         gpuHalo(&dd, box, &h_x, numAtomsTotal);
625
626         // Check results
627         checkResults1dHaloWith2Pulses(h_x.data(), &dd, numHomeAtoms);
628     }
629 }
630
631
632 TEST(HaloExchangeTest, Coordinates2dHaloWith1PulseInEachDim)
633 {
634     GMX_MPI_TEST(RequireRankCount<4>);
635
636     // Set up atom data
637     const int        numHomeAtoms  = 10;
638     const int        numHaloAtoms  = 4;
639     const int        numAtomsTotal = numHomeAtoms + numHaloAtoms;
640     HostVector<RVec> h_x;
641     h_x.resize(numAtomsTotal);
642
643     initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
644
645     // Set up dd
646     t_inputrec   ir;
647     gmx_domdec_t dd(ir);
648     dd.mpi_comm_all = MPI_COMM_WORLD;
649     gmx_domdec_comm_t comm;
650     dd.comm                      = &comm;
651     dd.unitCellInfo.haveScrewPBC = false;
652
653     DDAtomRanges atomRanges;
654     atomRanges.setEnd(DDAtomRanges::Type::Home, numHomeAtoms);
655     dd.comm->atomRanges = atomRanges;
656
657     define2dRankTopology(&dd);
658
659     std::vector<gmx_domdec_ind_t> indvec;
660     define2dHaloWith1PulseInEachDim(&dd, &indvec);
661
662     // Perform halo exchange
663     matrix box = { { 0., 0., 0. } };
664     dd_move_x(&dd, box, static_cast<ArrayRef<RVec>>(h_x), nullptr);
665
666     // Check results
667     checkResults2dHaloWith1PulseInEachDim(h_x.data(), &dd, numHomeAtoms);
668
669     if (GMX_GPU_CUDA && GMX_THREAD_MPI) // repeat with GPU halo codepath
670     {
671         // early return if no devices are available.
672         if (getTestHardwareEnvironment()->getTestDeviceList().empty())
673         {
674             return;
675         }
676
677         // Re-initialize input
678         initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
679
680         // Perform GPU halo exchange
681         gpuHalo(&dd, box, &h_x, numAtomsTotal);
682
683         // Check results
684         checkResults2dHaloWith1PulseInEachDim(h_x.data(), &dd, numHomeAtoms);
685     }
686 }
687
688 TEST(HaloExchangeTest, Coordinates2dHaloWith2PulsesInDim1)
689 {
690     GMX_MPI_TEST(RequireRankCount<4>);
691
692     // Set up atom data
693     const int        numHomeAtoms  = 10;
694     const int        numHaloAtoms  = 7;
695     const int        numAtomsTotal = numHomeAtoms + numHaloAtoms;
696     HostVector<RVec> h_x;
697     h_x.resize(numAtomsTotal);
698
699     initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
700
701     // Set up dd
702     t_inputrec   ir;
703     gmx_domdec_t dd(ir);
704     dd.mpi_comm_all = MPI_COMM_WORLD;
705     gmx_domdec_comm_t comm;
706     dd.comm                      = &comm;
707     dd.unitCellInfo.haveScrewPBC = false;
708
709     DDAtomRanges atomRanges;
710     atomRanges.setEnd(DDAtomRanges::Type::Home, numHomeAtoms);
711     dd.comm->atomRanges = atomRanges;
712
713     define2dRankTopology(&dd);
714
715     std::vector<gmx_domdec_ind_t> indvec;
716     define2dHaloWith2PulsesInDim1(&dd, &indvec);
717
718     // Perform halo exchange
719     matrix box = { { 0., 0., 0. } };
720     dd_move_x(&dd, box, static_cast<ArrayRef<RVec>>(h_x), nullptr);
721
722     // Check results
723     checkResults2dHaloWith2PulsesInDim1(h_x.data(), &dd, numHomeAtoms);
724
725     if (GMX_GPU_CUDA && GMX_THREAD_MPI) // repeat with GPU halo codepath
726     {
727         // early return if no devices are available.
728         if (getTestHardwareEnvironment()->getTestDeviceList().empty())
729         {
730             return;
731         }
732
733         // Re-initialize input
734         initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
735
736         // Perform GPU halo exchange
737         gpuHalo(&dd, box, &h_x, numAtomsTotal);
738
739         // Check results
740         checkResults2dHaloWith2PulsesInDim1(h_x.data(), &dd, numHomeAtoms);
741     }
742 }
743
744 } // namespace
745 } // namespace test
746 } // namespace gmx