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