9b3eb6678d3c4c4f92204c8b23f1031254329c54
[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, 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 <vector>
57
58 #include <gtest/gtest.h>
59
60 #include "gromacs/domdec/atomdistribution.h"
61 #include "gromacs/domdec/domdec_internal.h"
62 #include "gromacs/domdec/gpuhaloexchange.h"
63 #if GMX_GPU_CUDA
64 #    include "gromacs/gpu_utils/device_stream.h"
65 #    include "gromacs/gpu_utils/devicebuffer.h"
66 #    include "gromacs/gpu_utils/gpueventsynchronizer.cuh"
67 #endif
68 #include "gromacs/gpu_utils/hostallocator.h"
69 #include "gromacs/mdtypes/inputrec.h"
70
71 #include "testutils/mpitest.h"
72 #include "testutils/test_hardware_environment.h"
73
74 namespace gmx
75 {
76 namespace test
77 {
78 namespace
79 {
80
81 /*! \brief Get encoded numerical value for sending rank, atom number and spatial 3D index
82  *
83  * \param [in] sendRank         MPI rank of sender
84  * \param [in] atomNumber       Atom number
85  * \param [in] spatial3dIndex   Spatial 3D Index
86  *
87  * \returns                     Encoded value
88  */
89 float encodedValue(const int sendRank, const int atomNumber, const int spatial3dIndex)
90 {
91     return sendRank * 1000 + atomNumber * 100 + spatial3dIndex;
92 }
93
94 /*! \brief Initialize halo array
95  *
96  * \param [in] x              Atom coordinate data array
97  * \param [in] numHomeAtoms   Number of home atoms
98  * \param [in] numAtomsTotal  Total number of atoms, including halo
99  */
100 void initHaloData(RVec* x, const int numHomeAtoms, const int numAtomsTotal)
101 {
102     int rank;
103     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
104
105     for (int i = 0; i < numAtomsTotal; i++)
106     {
107         for (int j = 0; j < DIM; j++)
108         {
109             x[i][j] = i < numHomeAtoms ? encodedValue(rank, i, j) : -1;
110         }
111     }
112 }
113
114 /*! \brief Perform GPU halo exchange, including required setup and data transfers
115  *
116  * \param [in] dd             Domain decomposition object
117  * \param [in] box            Box matrix
118  * \param [in] h_x            Atom coordinate data array on host
119  * \param [in] numAtomsTotal  Total number of atoms, including halo
120  */
121 void gpuHalo(gmx_domdec_t* dd, matrix box, HostVector<RVec>* h_x, int numAtomsTotal)
122 {
123 #if (GMX_GPU_CUDA && GMX_THREAD_MPI)
124     // pin memory if possible
125     changePinningPolicy(h_x, PinningPolicy::PinnedIfSupported);
126     // Set up GPU hardware environment and assign this MPI rank to a device
127     int rank;
128     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
129     int         numDevices = getTestHardwareEnvironment()->getTestDeviceList().size();
130     const auto& testDevice = getTestHardwareEnvironment()->getTestDeviceList()[rank % numDevices];
131     const auto& deviceContext = testDevice->deviceContext();
132     setActiveDevice(testDevice->deviceInfo());
133     DeviceStream deviceStream(deviceContext, DeviceStreamPriority::Normal, false);
134
135     // Set up GPU buffer and copy input data from host
136     DeviceBuffer<RVec> d_x;
137     int                d_x_size       = -1;
138     int                d_x_size_alloc = -1;
139     reallocateDeviceBuffer(&d_x, numAtomsTotal, &d_x_size, &d_x_size_alloc, deviceContext);
140
141     copyToDeviceBuffer(&d_x, h_x->data(), 0, numAtomsTotal, deviceStream, GpuApiCallBehavior::Sync, nullptr);
142
143     GpuEventSynchronizer coordinatesReadyOnDeviceEvent;
144     coordinatesReadyOnDeviceEvent.markEvent(deviceStream);
145
146     std::array<std::vector<GpuHaloExchange>, DIM> gpuHaloExchange;
147
148     // Create halo exchange objects
149     for (int d = 0; d < dd->ndim; d++)
150     {
151         for (int pulse = 0; pulse < dd->comm->cd[d].numPulses(); pulse++)
152         {
153             gpuHaloExchange[d].push_back(GpuHaloExchange(dd, d, MPI_COMM_WORLD, deviceContext,
154                                                          deviceStream, deviceStream, pulse, nullptr));
155         }
156     }
157
158     // Perform GPU halo exchange
159     for (int d = 0; d < dd->ndim; d++)
160     {
161         for (int pulse = 0; pulse < dd->comm->cd[d].numPulses(); pulse++)
162         {
163             gpuHaloExchange[d][pulse].reinitHalo(d_x, nullptr);
164             gpuHaloExchange[d][pulse].communicateHaloCoordinates(box, &coordinatesReadyOnDeviceEvent);
165         }
166     }
167
168     GpuEventSynchronizer haloCompletedEvent;
169     haloCompletedEvent.markEvent(deviceStream);
170     haloCompletedEvent.waitForEvent();
171
172     // Copy results back to host
173     copyFromDeviceBuffer(h_x->data(), &d_x, 0, numAtomsTotal, deviceStream,
174                          GpuApiCallBehavior::Sync, nullptr);
175
176     freeDeviceBuffer(d_x);
177 #else
178     GMX_UNUSED_VALUE(dd);
179     GMX_UNUSED_VALUE(box);
180     GMX_UNUSED_VALUE(h_x);
181     GMX_UNUSED_VALUE(numAtomsTotal);
182 #endif
183 }
184
185 /*! \brief Define 1D rank topology with 4 MPI tasks
186  *
187  * \param [in] dd  Domain decomposition object
188  */
189 void define1dRankTopology(gmx_domdec_t* dd)
190 {
191     int rank;
192     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
193
194     dd->neighbor[0][0] = (rank + 1) % 4;
195     dd->neighbor[0][1] = (rank == 0) ? 3 : rank - 1;
196 }
197
198 /*! \brief Define 2D rank topology with 4 MPI tasks
199  *
200  *    -----
201  *   | 2 3 |
202  *   | 0 1 |
203  *    -----
204  *
205  * \param [in] dd  Domain decomposition object
206  */
207 void define2dRankTopology(gmx_domdec_t* dd)
208 {
209
210     int rank;
211     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
212
213     switch (rank)
214     {
215         case 0:
216             dd->neighbor[0][0] = 1;
217             dd->neighbor[0][1] = 1;
218             dd->neighbor[1][0] = 2;
219             dd->neighbor[1][1] = 2;
220             break;
221         case 1:
222             dd->neighbor[0][0] = 0;
223             dd->neighbor[0][1] = 0;
224             dd->neighbor[1][0] = 3;
225             dd->neighbor[1][1] = 3;
226             break;
227         case 2:
228             dd->neighbor[0][0] = 3;
229             dd->neighbor[0][1] = 3;
230             dd->neighbor[1][0] = 0;
231             dd->neighbor[1][1] = 0;
232             break;
233         case 3:
234             dd->neighbor[0][0] = 2;
235             dd->neighbor[0][1] = 2;
236             dd->neighbor[1][0] = 1;
237             dd->neighbor[1][1] = 1;
238             break;
239     }
240 }
241
242 /*! \brief Define a 1D halo with 1 pulses
243  *
244  * \param [in] dd      Domain decomposition object
245  * \param [in] indvec  Vector of index vectors
246  */
247 void define1dHaloWith1Pulse(gmx_domdec_t* dd, std::vector<gmx_domdec_ind_t>* indvec)
248 {
249
250     int rank;
251     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
252
253     std::vector<int> indexvec;
254     gmx_domdec_ind_t ind;
255
256     dd->ndim     = 1;
257     int nzone    = 1;
258     int dimIndex = 0;
259
260     // Set up indices involved in halo
261     indexvec.clear();
262     indvec->clear();
263
264     dd->comm->cd[dimIndex].receiveInPlace = true;
265     dd->dim[dimIndex]                     = 0;
266     dd->ci[dimIndex]                      = rank;
267
268     // First pulse involves (arbitrary) indices 1 and 3
269     indexvec.push_back(1);
270     indexvec.push_back(3);
271
272     ind.index            = indexvec;
273     ind.nsend[nzone + 1] = 2;
274     ind.nrecv[nzone + 1] = 2;
275     indvec->push_back(ind);
276
277     dd->comm->cd[dimIndex].ind = *indvec;
278 }
279
280 /*! \brief Define a 1D halo with 2 pulses
281  *
282  * \param [in] dd      Domain decomposition object
283  * \param [in] indvec  Vector of index vectors
284  */
285 void define1dHaloWith2Pulses(gmx_domdec_t* dd, std::vector<gmx_domdec_ind_t>* indvec)
286 {
287
288     int rank;
289     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
290
291     std::vector<int> indexvec;
292     gmx_domdec_ind_t ind;
293
294     dd->ndim     = 1;
295     int nzone    = 1;
296     int dimIndex = 0;
297
298     // Set up indices involved in halo
299     indexvec.clear();
300     indvec->clear();
301
302     dd->comm->cd[dimIndex].receiveInPlace = true;
303     dd->dim[dimIndex]                     = 0;
304     dd->ci[dimIndex]                      = rank;
305
306     // First pulse involves (arbitrary) indices 1 and 3
307     indexvec.push_back(1);
308     indexvec.push_back(3);
309
310     ind.index            = indexvec;
311     ind.nsend[nzone + 1] = 2;
312     ind.nrecv[nzone + 1] = 2;
313     indvec->push_back(ind);
314
315     // Add another pulse with (arbitrary) indices 4,5,7
316     indexvec.clear();
317
318     indexvec.push_back(4);
319     indexvec.push_back(5);
320     indexvec.push_back(7);
321
322     ind.index            = indexvec;
323     ind.nsend[nzone + 1] = 3;
324     ind.nrecv[nzone + 1] = 3;
325     indvec->push_back(ind);
326
327     dd->comm->cd[dimIndex].ind = *indvec;
328 }
329
330 /*! \brief Define a 2D halo with 1 pulse in each dimension
331  *
332  * \param [in] dd      Domain decomposition object
333  * \param [in] indvec  Vector of index vectors
334  */
335 void define2dHaloWith1PulseInEachDim(gmx_domdec_t* dd, std::vector<gmx_domdec_ind_t>* indvec)
336 {
337
338     int rank;
339     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
340
341     std::vector<int> indexvec;
342     gmx_domdec_ind_t ind;
343
344     dd->ndim  = 2;
345     int nzone = 1;
346     for (int dimIndex = 0; dimIndex < dd->ndim; dimIndex++)
347     {
348
349         // Set up indices involved in halo
350         indexvec.clear();
351         indvec->clear();
352
353         dd->comm->cd[dimIndex].receiveInPlace = true;
354         dd->dim[dimIndex]                     = 0;
355         dd->ci[dimIndex]                      = rank;
356
357         // Single pulse involving (arbitrary) indices 1 and 3
358         indexvec.push_back(1);
359         indexvec.push_back(3);
360
361         ind.index            = indexvec;
362         ind.nsend[nzone + 1] = 2;
363         ind.nrecv[nzone + 1] = 2;
364         indvec->push_back(ind);
365
366         dd->comm->cd[dimIndex].ind = *indvec;
367
368         nzone += nzone;
369     }
370 }
371
372 /*! \brief Define a 2D halo with 2 pulses in the first dimension
373  *
374  * \param [in] dd      Domain decomposition object
375  * \param [in] indvec  Vector of index vectors
376  */
377 void define2dHaloWith2PulsesInDim1(gmx_domdec_t* dd, std::vector<gmx_domdec_ind_t>* indvec)
378 {
379
380     int rank;
381     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
382
383     std::vector<int> indexvec;
384     gmx_domdec_ind_t ind;
385
386     dd->ndim  = 2;
387     int nzone = 1;
388     for (int dimIndex = 0; dimIndex < dd->ndim; dimIndex++)
389     {
390
391         // Set up indices involved in halo
392         indexvec.clear();
393         indvec->clear();
394
395         dd->comm->cd[dimIndex].receiveInPlace = true;
396         dd->dim[dimIndex]                     = 0;
397         dd->ci[dimIndex]                      = rank;
398
399         // First pulse involves (arbitrary) indices 1 and 3
400         indexvec.push_back(1);
401         indexvec.push_back(3);
402
403         ind.index            = indexvec;
404         ind.nsend[nzone + 1] = 2;
405         ind.nrecv[nzone + 1] = 2;
406         indvec->push_back(ind);
407
408         if (dimIndex == 0) // Add another pulse with (arbitrary) indices 4,5,7
409         {
410             indexvec.clear();
411
412             indexvec.push_back(4);
413             indexvec.push_back(5);
414             indexvec.push_back(7);
415
416             ind.index            = indexvec;
417             ind.nsend[nzone + 1] = 3;
418             ind.nrecv[nzone + 1] = 3;
419             indvec->push_back(ind);
420         }
421
422         dd->comm->cd[dimIndex].ind = *indvec;
423
424         nzone += nzone;
425     }
426 }
427
428 /*! \brief Check results for above-defined 1D halo with 1 pulse
429  *
430  * \param [in] x             Atom coordinate data array
431  * \param [in] dd            Domain decomposition object
432  * \param [in] numHomeAtoms  Number of home atoms
433  */
434 void checkResults1dHaloWith1Pulse(const RVec* x, const gmx_domdec_t* dd, const int numHomeAtoms)
435 {
436     // Check results are expected from values encoded in x data
437     for (int j = 0; j < DIM; j++)
438     {
439         // First Pulse in first dim: atoms 1 and 3 from forward horizontal neighbour
440         EXPECT_EQ(x[numHomeAtoms][j], encodedValue(dd->neighbor[0][0], 1, j));
441         EXPECT_EQ(x[numHomeAtoms + 1][j], encodedValue(dd->neighbor[0][0], 3, j));
442     }
443 }
444
445 /*! \brief Check results for above-defined 1D halo with 2 pulses
446  *
447  * \param [in] x             Atom coordinate data array
448  * \param [in] dd            Domain decomposition object
449  * \param [in] numHomeAtoms  Number of home atoms
450  */
451 void checkResults1dHaloWith2Pulses(const RVec* x, const gmx_domdec_t* dd, const int numHomeAtoms)
452 {
453     // Check results are expected from values encoded in x data
454     for (int j = 0; j < DIM; j++)
455     {
456         // First Pulse in first dim: atoms 1 and 3 from forward horizontal neighbour
457         EXPECT_EQ(x[numHomeAtoms][j], encodedValue(dd->neighbor[0][0], 1, j));
458         EXPECT_EQ(x[numHomeAtoms + 1][j], encodedValue(dd->neighbor[0][0], 3, j));
459         // Second Pulse in first dim: atoms 4,5,7 from forward horizontal neighbour
460         EXPECT_EQ(x[numHomeAtoms + 2][j], encodedValue(dd->neighbor[0][0], 4, j));
461         EXPECT_EQ(x[numHomeAtoms + 3][j], encodedValue(dd->neighbor[0][0], 5, j));
462         EXPECT_EQ(x[numHomeAtoms + 4][j], encodedValue(dd->neighbor[0][0], 7, j));
463     }
464 }
465
466 /*! \brief Check results for above-defined 2D halo with 1 pulse in each dimension
467  *
468  * \param [in] x             Atom coordinate data array
469  * \param [in] dd            Domain decomposition object
470  * \param [in] numHomeAtoms  Number of home atoms
471  */
472 void checkResults2dHaloWith1PulseInEachDim(const RVec* x, const gmx_domdec_t* dd, const int numHomeAtoms)
473 {
474     // Check results are expected from values encoded in x data
475     for (int j = 0; j < DIM; j++)
476     {
477         // First Pulse in first dim: atoms 1 and 3 from forward horizontal neighbour
478         EXPECT_EQ(x[numHomeAtoms][j], encodedValue(dd->neighbor[0][0], 1, j));
479         EXPECT_EQ(x[numHomeAtoms + 1][j], encodedValue(dd->neighbor[0][0], 3, j));
480         // First Pulse in second dim: atoms 1 and 3 from forward vertical neighbour
481         EXPECT_EQ(x[numHomeAtoms + 2][j], encodedValue(dd->neighbor[1][0], 1, j));
482         EXPECT_EQ(x[numHomeAtoms + 3][j], encodedValue(dd->neighbor[1][0], 3, j));
483     }
484 }
485
486 /*! \brief Check results for above-defined 2D halo with 2 pulses in the first dimension
487  *
488  * \param [in] x             Atom coordinate data array
489  * \param [in] dd            Domain decomposition object
490  * \param [in] numHomeAtoms  Number of home atoms
491  */
492 void checkResults2dHaloWith2PulsesInDim1(const RVec* x, const gmx_domdec_t* dd, const int numHomeAtoms)
493 {
494     // Check results are expected from values encoded in x data
495     for (int j = 0; j < DIM; j++)
496     {
497         // First Pulse in first dim: atoms 1 and 3 from forward horizontal neighbour
498         EXPECT_EQ(x[numHomeAtoms][j], encodedValue(dd->neighbor[0][0], 1, j));
499         EXPECT_EQ(x[numHomeAtoms + 1][j], encodedValue(dd->neighbor[0][0], 3, j));
500         // Second Pulse in first dim: atoms 4,5,7 from forward horizontal neighbour
501         EXPECT_EQ(x[numHomeAtoms + 2][j], encodedValue(dd->neighbor[0][0], 4, j));
502         EXPECT_EQ(x[numHomeAtoms + 3][j], encodedValue(dd->neighbor[0][0], 5, j));
503         EXPECT_EQ(x[numHomeAtoms + 4][j], encodedValue(dd->neighbor[0][0], 7, j));
504         // First Pulse in second dim: atoms 1 and 3 from forward vertical neighbour
505         EXPECT_EQ(x[numHomeAtoms + 5][j], encodedValue(dd->neighbor[1][0], 1, j));
506         EXPECT_EQ(x[numHomeAtoms + 6][j], encodedValue(dd->neighbor[1][0], 3, j));
507     }
508 }
509
510 TEST(HaloExchangeTest, Coordinates1dHaloWith1Pulse)
511 {
512     GMX_MPI_TEST(4);
513
514     // Set up atom data
515     const int        numHomeAtoms  = 10;
516     const int        numHaloAtoms  = 2;
517     const int        numAtomsTotal = numHomeAtoms + numHaloAtoms;
518     HostVector<RVec> h_x;
519     h_x.resize(numAtomsTotal);
520
521     initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
522
523     // Set up dd
524     t_inputrec   ir;
525     gmx_domdec_t dd(ir);
526     dd.mpi_comm_all = MPI_COMM_WORLD;
527     gmx_domdec_comm_t comm;
528     dd.comm                      = &comm;
529     dd.unitCellInfo.haveScrewPBC = false;
530
531     DDAtomRanges atomRanges;
532     atomRanges.setEnd(DDAtomRanges::Type::Home, numHomeAtoms);
533     dd.comm->atomRanges = atomRanges;
534
535     define1dRankTopology(&dd);
536
537     std::vector<gmx_domdec_ind_t> indvec;
538     define1dHaloWith1Pulse(&dd, &indvec);
539
540     // Perform halo exchange
541     matrix box = { { 0., 0., 0. } };
542     dd_move_x(&dd, box, static_cast<ArrayRef<RVec>>(h_x), nullptr);
543
544     // Check results
545     checkResults1dHaloWith1Pulse(h_x.data(), &dd, numHomeAtoms);
546
547     if (GMX_GPU_CUDA && GMX_THREAD_MPI) // repeat with GPU halo codepath
548     {
549         // early return if no devices are available.
550         if (getTestHardwareEnvironment()->getTestDeviceList().empty())
551         {
552             return;
553         }
554
555         // Re-initialize input
556         initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
557
558         // Perform GPU halo exchange
559         gpuHalo(&dd, box, &h_x, numAtomsTotal);
560
561         // Check results
562         checkResults1dHaloWith1Pulse(h_x.data(), &dd, numHomeAtoms);
563     }
564 }
565
566 TEST(HaloExchangeTest, Coordinates1dHaloWith2Pulses)
567 {
568     GMX_MPI_TEST(4);
569
570     // Set up atom data
571     const int        numHomeAtoms  = 10;
572     const int        numHaloAtoms  = 5;
573     const int        numAtomsTotal = numHomeAtoms + numHaloAtoms;
574     HostVector<RVec> h_x;
575     h_x.resize(numAtomsTotal);
576
577     initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
578
579     // Set up dd
580     t_inputrec   ir;
581     gmx_domdec_t dd(ir);
582     dd.mpi_comm_all = MPI_COMM_WORLD;
583     gmx_domdec_comm_t comm;
584     dd.comm                      = &comm;
585     dd.unitCellInfo.haveScrewPBC = false;
586
587     DDAtomRanges atomRanges;
588     atomRanges.setEnd(DDAtomRanges::Type::Home, numHomeAtoms);
589     dd.comm->atomRanges = atomRanges;
590
591     define1dRankTopology(&dd);
592
593     std::vector<gmx_domdec_ind_t> indvec;
594     define1dHaloWith2Pulses(&dd, &indvec);
595
596     // Perform halo exchange
597     matrix box = { { 0., 0., 0. } };
598     dd_move_x(&dd, box, static_cast<ArrayRef<RVec>>(h_x), nullptr);
599
600     // Check results
601     checkResults1dHaloWith2Pulses(h_x.data(), &dd, numHomeAtoms);
602
603     if (GMX_GPU_CUDA && GMX_THREAD_MPI) // repeat with GPU halo codepath
604     {
605         // early return if no devices are available.
606         if (getTestHardwareEnvironment()->getTestDeviceList().empty())
607         {
608             return;
609         }
610
611         // Re-initialize input
612         initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
613
614         // Perform GPU halo exchange
615         gpuHalo(&dd, box, &h_x, numAtomsTotal);
616
617         // Check results
618         checkResults1dHaloWith2Pulses(h_x.data(), &dd, numHomeAtoms);
619     }
620 }
621
622
623 TEST(HaloExchangeTest, Coordinates2dHaloWith1PulseInEachDim)
624 {
625     GMX_MPI_TEST(4);
626
627     // Set up atom data
628     const int        numHomeAtoms  = 10;
629     const int        numHaloAtoms  = 4;
630     const int        numAtomsTotal = numHomeAtoms + numHaloAtoms;
631     HostVector<RVec> h_x;
632     h_x.resize(numAtomsTotal);
633
634     initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
635
636     // Set up dd
637     t_inputrec   ir;
638     gmx_domdec_t dd(ir);
639     dd.mpi_comm_all = MPI_COMM_WORLD;
640     gmx_domdec_comm_t comm;
641     dd.comm                      = &comm;
642     dd.unitCellInfo.haveScrewPBC = false;
643
644     DDAtomRanges atomRanges;
645     atomRanges.setEnd(DDAtomRanges::Type::Home, numHomeAtoms);
646     dd.comm->atomRanges = atomRanges;
647
648     define2dRankTopology(&dd);
649
650     std::vector<gmx_domdec_ind_t> indvec;
651     define2dHaloWith1PulseInEachDim(&dd, &indvec);
652
653     // Perform halo exchange
654     matrix box = { { 0., 0., 0. } };
655     dd_move_x(&dd, box, static_cast<ArrayRef<RVec>>(h_x), nullptr);
656
657     // Check results
658     checkResults2dHaloWith1PulseInEachDim(h_x.data(), &dd, numHomeAtoms);
659
660     if (GMX_GPU_CUDA && GMX_THREAD_MPI) // repeat with GPU halo codepath
661     {
662         // early return if no devices are available.
663         if (getTestHardwareEnvironment()->getTestDeviceList().empty())
664         {
665             return;
666         }
667
668         // Re-initialize input
669         initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
670
671         // Perform GPU halo exchange
672         gpuHalo(&dd, box, &h_x, numAtomsTotal);
673
674         // Check results
675         checkResults2dHaloWith1PulseInEachDim(h_x.data(), &dd, numHomeAtoms);
676     }
677 }
678
679 TEST(HaloExchangeTest, Coordinates2dHaloWith2PulsesInDim1)
680 {
681     GMX_MPI_TEST(4);
682
683     // Set up atom data
684     const int        numHomeAtoms  = 10;
685     const int        numHaloAtoms  = 7;
686     const int        numAtomsTotal = numHomeAtoms + numHaloAtoms;
687     HostVector<RVec> h_x;
688     h_x.resize(numAtomsTotal);
689
690     initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
691
692     // Set up dd
693     t_inputrec   ir;
694     gmx_domdec_t dd(ir);
695     dd.mpi_comm_all = MPI_COMM_WORLD;
696     gmx_domdec_comm_t comm;
697     dd.comm                      = &comm;
698     dd.unitCellInfo.haveScrewPBC = false;
699
700     DDAtomRanges atomRanges;
701     atomRanges.setEnd(DDAtomRanges::Type::Home, numHomeAtoms);
702     dd.comm->atomRanges = atomRanges;
703
704     define2dRankTopology(&dd);
705
706     std::vector<gmx_domdec_ind_t> indvec;
707     define2dHaloWith2PulsesInDim1(&dd, &indvec);
708
709     // Perform halo exchange
710     matrix box = { { 0., 0., 0. } };
711     dd_move_x(&dd, box, static_cast<ArrayRef<RVec>>(h_x), nullptr);
712
713     // Check results
714     checkResults2dHaloWith2PulsesInDim1(h_x.data(), &dd, numHomeAtoms);
715
716     if (GMX_GPU_CUDA && GMX_THREAD_MPI) // repeat with GPU halo codepath
717     {
718         // early return if no devices are available.
719         if (getTestHardwareEnvironment()->getTestDeviceList().empty())
720         {
721             return;
722         }
723
724         // Re-initialize input
725         initHaloData(h_x.data(), numHomeAtoms, numAtomsTotal);
726
727         // Perform GPU halo exchange
728         gpuHalo(&dd, box, &h_x, numAtomsTotal);
729
730         // Check results
731         checkResults2dHaloWith2PulsesInDim1(h_x.data(), &dd, numHomeAtoms);
732     }
733 }
734
735 } // namespace
736 } // namespace test
737 } // namespace gmx