Make use of the DeviceStreamManager
[alexxy/gromacs.git] / src / gromacs / ewald / tests / pmetestcommon.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016,2017,2018,2019,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
37  * Implements common routines for PME tests.
38  *
39  * \author Aleksei Iupinov <a.yupinov@gmail.com>
40  * \ingroup module_ewald
41  */
42 #include "gmxpre.h"
43
44 #include "pmetestcommon.h"
45
46 #include <cstring>
47
48 #include <algorithm>
49
50 #include "gromacs/domdec/domdec.h"
51 #include "gromacs/ewald/pme_gather.h"
52 #include "gromacs/ewald/pme_gpu_calculate_splines.h"
53 #include "gromacs/ewald/pme_gpu_constants.h"
54 #include "gromacs/ewald/pme_gpu_internal.h"
55 #include "gromacs/ewald/pme_gpu_staging.h"
56 #include "gromacs/ewald/pme_grid.h"
57 #include "gromacs/ewald/pme_internal.h"
58 #include "gromacs/ewald/pme_redistribute.h"
59 #include "gromacs/ewald/pme_solve.h"
60 #include "gromacs/ewald/pme_spread.h"
61 #include "gromacs/fft/parallel_3dfft.h"
62 #include "gromacs/gpu_utils/device_stream_manager.h"
63 #include "gromacs/gpu_utils/gpu_utils.h"
64 #include "gromacs/math/invertmatrix.h"
65 #include "gromacs/mdtypes/commrec.h"
66 #include "gromacs/pbcutil/pbc.h"
67 #include "gromacs/topology/topology.h"
68 #include "gromacs/utility/exceptions.h"
69 #include "gromacs/utility/gmxassert.h"
70 #include "gromacs/utility/logger.h"
71 #include "gromacs/utility/stringutil.h"
72
73 #include "testutils/testasserts.h"
74
75 #include "testhardwarecontexts.h"
76
77 namespace gmx
78 {
79 namespace test
80 {
81
82 bool pmeSupportsInputForMode(const gmx_hw_info_t& hwinfo, const t_inputrec* inputRec, CodePath mode)
83 {
84     bool       implemented;
85     gmx_mtop_t mtop;
86     switch (mode)
87     {
88         case CodePath::CPU: implemented = true; break;
89
90         case CodePath::GPU:
91             implemented = (pme_gpu_supports_build(nullptr) && pme_gpu_supports_hardware(hwinfo, nullptr)
92                            && pme_gpu_supports_input(*inputRec, mtop, nullptr));
93             break;
94
95         default: GMX_THROW(InternalError("Test not implemented for this mode"));
96     }
97     return implemented;
98 }
99
100 uint64_t getSplineModuliDoublePrecisionUlps(int splineOrder)
101 {
102     /* Arbitrary ulp tolerance for sine/cosine implementation. It's
103      * hard to know what to pick without testing lots of
104      * implementations. */
105     const uint64_t sineUlps = 10;
106     return 4 * (splineOrder - 2) + 2 * sineUlps * splineOrder;
107 }
108
109 //! PME initialization
110 PmeSafePointer pmeInitWrapper(const t_inputrec*    inputRec,
111                               const CodePath       mode,
112                               const DeviceContext* deviceContext,
113                               const DeviceStream*  deviceStream,
114                               const PmeGpuProgram* pmeGpuProgram,
115                               const Matrix3x3&     box,
116                               const real           ewaldCoeff_q,
117                               const real           ewaldCoeff_lj)
118 {
119     const MDLogger dummyLogger;
120     const auto     runMode       = (mode == CodePath::CPU) ? PmeRunMode::CPU : PmeRunMode::Mixed;
121     t_commrec      dummyCommrec  = { 0 };
122     NumPmeDomains  numPmeDomains = { 1, 1 };
123     gmx_pme_t* pmeDataRaw = gmx_pme_init(&dummyCommrec, numPmeDomains, inputRec, false, false, true,
124                                          ewaldCoeff_q, ewaldCoeff_lj, 1, runMode, nullptr,
125                                          deviceContext, deviceStream, pmeGpuProgram, dummyLogger);
126     PmeSafePointer pme(pmeDataRaw); // taking ownership
127
128     // TODO get rid of this with proper matrix type
129     matrix boxTemp;
130     for (int i = 0; i < DIM; i++)
131     {
132         for (int j = 0; j < DIM; j++)
133         {
134             boxTemp[i][j] = box[i * DIM + j];
135         }
136     }
137     const char* boxError = check_box(PbcType::Unset, boxTemp);
138     GMX_RELEASE_ASSERT(boxError == nullptr, boxError);
139
140     switch (mode)
141     {
142         case CodePath::CPU: invertBoxMatrix(boxTemp, pme->recipbox); break;
143
144         case CodePath::GPU:
145             pme_gpu_set_testing(pme->gpu, true);
146             pme_gpu_update_input_box(pme->gpu, boxTemp);
147             break;
148
149         default: GMX_THROW(InternalError("Test not implemented for this mode"));
150     }
151
152     return pme;
153 }
154
155 //! Simple PME initialization based on input, no atom data
156 PmeSafePointer pmeInitEmpty(const t_inputrec*    inputRec,
157                             const CodePath       mode,
158                             const DeviceContext* deviceContext,
159                             const DeviceStream*  deviceStream,
160                             const PmeGpuProgram* pmeGpuProgram,
161                             const Matrix3x3&     box,
162                             const real           ewaldCoeff_q,
163                             const real           ewaldCoeff_lj)
164 {
165     return pmeInitWrapper(inputRec, mode, deviceContext, deviceStream, pmeGpuProgram, box,
166                           ewaldCoeff_q, ewaldCoeff_lj);
167     // hiding the fact that PME actually needs to know the number of atoms in advance
168 }
169
170 PmeSafePointer pmeInitEmpty(const t_inputrec* inputRec)
171 {
172     const Matrix3x3 defaultBox = { { 1.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 1.0F } };
173     return pmeInitWrapper(inputRec, CodePath::CPU, nullptr, nullptr, nullptr, defaultBox, 0.0F, 0.0F);
174 }
175
176 //! Make a GPU state-propagator manager
177 std::unique_ptr<StatePropagatorDataGpu> makeStatePropagatorDataGpu(const gmx_pme_t&     pme,
178                                                                    const DeviceContext* deviceContext,
179                                                                    const DeviceStream* deviceStream)
180 {
181     // TODO: Pin the host buffer and use async memory copies
182     // TODO: Special constructor for PME-only rank / PME-tests is used here. There should be a mechanism to
183     //       restrict one from using other constructor here.
184     return std::make_unique<StatePropagatorDataGpu>(deviceStream, *deviceContext, GpuApiCallBehavior::Sync,
185                                                     pme_gpu_get_block_size(&pme), nullptr);
186 }
187
188 //! PME initialization with atom data
189 void pmeInitAtoms(gmx_pme_t*               pme,
190                   StatePropagatorDataGpu*  stateGpu,
191                   const CodePath           mode,
192                   const CoordinatesVector& coordinates,
193                   const ChargesVector&     charges)
194 {
195     const index atomCount = coordinates.size();
196     GMX_RELEASE_ASSERT(atomCount == charges.ssize(), "Mismatch in atom data");
197     PmeAtomComm* atc = nullptr;
198
199     switch (mode)
200     {
201         case CodePath::CPU:
202             atc              = &(pme->atc[0]);
203             atc->x           = coordinates;
204             atc->coefficient = charges;
205             gmx_pme_reinit_atoms(pme, atomCount, charges.data());
206             /* With decomposition there would be more boilerplate atc code here, e.g. do_redist_pos_coeffs */
207             break;
208
209         case CodePath::GPU:
210             // TODO: Avoid use of atc in the GPU code path
211             atc = &(pme->atc[0]);
212             // We need to set atc->n for passing the size in the tests
213             atc->setNumAtoms(atomCount);
214             gmx_pme_reinit_atoms(pme, atomCount, charges.data());
215
216             stateGpu->reinit(atomCount, atomCount);
217             stateGpu->copyCoordinatesToGpu(arrayRefFromArray(coordinates.data(), coordinates.size()),
218                                            gmx::AtomLocality::All);
219             pme_gpu_set_kernelparam_coordinates(pme->gpu, stateGpu->getCoordinates());
220
221             break;
222
223         default: GMX_THROW(InternalError("Test not implemented for this mode"));
224     }
225 }
226
227 //! Getting local PME real grid pointer for test I/O
228 static real* pmeGetRealGridInternal(const gmx_pme_t* pme)
229 {
230     const size_t gridIndex = 0;
231     return pme->fftgrid[gridIndex];
232 }
233
234 //! Getting local PME real grid dimensions
235 static void pmeGetRealGridSizesInternal(const gmx_pme_t* pme,
236                                         CodePath         mode,
237                                         IVec& gridSize,       //NOLINT(google-runtime-references)
238                                         IVec& paddedGridSize) //NOLINT(google-runtime-references)
239 {
240     const size_t gridIndex = 0;
241     IVec         gridOffsetUnused;
242     switch (mode)
243     {
244         case CodePath::CPU:
245             gmx_parallel_3dfft_real_limits(pme->pfft_setup[gridIndex], gridSize, gridOffsetUnused,
246                                            paddedGridSize);
247             break;
248
249         case CodePath::GPU:
250             pme_gpu_get_real_grid_sizes(pme->gpu, &gridSize, &paddedGridSize);
251             break;
252
253         default: GMX_THROW(InternalError("Test not implemented for this mode"));
254     }
255 }
256
257 //! Getting local PME complex grid pointer for test I/O
258 static t_complex* pmeGetComplexGridInternal(const gmx_pme_t* pme)
259 {
260     const size_t gridIndex = 0;
261     return pme->cfftgrid[gridIndex];
262 }
263
264 //! Getting local PME complex grid dimensions
265 static void pmeGetComplexGridSizesInternal(const gmx_pme_t* pme,
266                                            IVec& gridSize,       //NOLINT(google-runtime-references)
267                                            IVec& paddedGridSize) //NOLINT(google-runtime-references)
268 {
269     const size_t gridIndex = 0;
270     IVec         gridOffsetUnused, complexOrderUnused;
271     gmx_parallel_3dfft_complex_limits(pme->pfft_setup[gridIndex], complexOrderUnused, gridSize,
272                                       gridOffsetUnused, paddedGridSize); // TODO: what about YZX ordering?
273 }
274
275 //! Getting the PME grid memory buffer and its sizes - template definition
276 template<typename ValueType>
277 static void pmeGetGridAndSizesInternal(const gmx_pme_t* /*unused*/,
278                                        CodePath /*unused*/,
279                                        ValueType*& /*unused*/, //NOLINT(google-runtime-references)
280                                        IVec& /*unused*/,       //NOLINT(google-runtime-references)
281                                        IVec& /*unused*/)       //NOLINT(google-runtime-references)
282 {
283     GMX_THROW(InternalError("Deleted function call"));
284     // explicitly deleting general template does not compile in clang/icc, see https://llvm.org/bugs/show_bug.cgi?id=17537
285 }
286
287 //! Getting the PME real grid memory buffer and its sizes
288 template<>
289 void pmeGetGridAndSizesInternal<real>(const gmx_pme_t* pme, CodePath mode, real*& grid, IVec& gridSize, IVec& paddedGridSize)
290 {
291     grid = pmeGetRealGridInternal(pme);
292     pmeGetRealGridSizesInternal(pme, mode, gridSize, paddedGridSize);
293 }
294
295 //! Getting the PME complex grid memory buffer and its sizes
296 template<>
297 void pmeGetGridAndSizesInternal<t_complex>(const gmx_pme_t* pme,
298                                            CodePath /*unused*/,
299                                            t_complex*& grid,
300                                            IVec&       gridSize,
301                                            IVec&       paddedGridSize)
302 {
303     grid = pmeGetComplexGridInternal(pme);
304     pmeGetComplexGridSizesInternal(pme, gridSize, paddedGridSize);
305 }
306
307 //! PME spline calculation and charge spreading
308 void pmePerformSplineAndSpread(gmx_pme_t* pme,
309                                CodePath   mode, // TODO const qualifiers elsewhere
310                                bool       computeSplines,
311                                bool       spreadCharges)
312 {
313     GMX_RELEASE_ASSERT(pme != nullptr, "PME data is not initialized");
314     PmeAtomComm* atc                          = &(pme->atc[0]);
315     const size_t gridIndex                    = 0;
316     const bool   computeSplinesForZeroCharges = true;
317     real*        fftgrid                      = spreadCharges ? pme->fftgrid[gridIndex] : nullptr;
318     real*        pmegrid                      = pme->pmegrid[gridIndex].grid.grid;
319
320     switch (mode)
321     {
322         case CodePath::CPU:
323             spread_on_grid(pme, atc, &pme->pmegrid[gridIndex], computeSplines, spreadCharges,
324                            fftgrid, computeSplinesForZeroCharges, gridIndex);
325             if (spreadCharges && !pme->bUseThreads)
326             {
327                 wrap_periodic_pmegrid(pme, pmegrid);
328                 copy_pmegrid_to_fftgrid(pme, pmegrid, fftgrid, gridIndex);
329             }
330             break;
331
332         case CodePath::GPU:
333         {
334             // no synchronization needed as x is transferred in the PME stream
335             GpuEventSynchronizer* xReadyOnDevice = nullptr;
336             pme_gpu_spread(pme->gpu, xReadyOnDevice, gridIndex, fftgrid, computeSplines, spreadCharges);
337         }
338         break;
339
340         default: GMX_THROW(InternalError("Test not implemented for this mode"));
341     }
342 }
343
344 //! Getting the internal spline data buffer pointer
345 static real* pmeGetSplineDataInternal(const gmx_pme_t* pme, PmeSplineDataType type, int dimIndex)
346 {
347     GMX_ASSERT((0 <= dimIndex) && (dimIndex < DIM), "Invalid dimension index");
348     const PmeAtomComm* atc          = &(pme->atc[0]);
349     const size_t       threadIndex  = 0;
350     real*              splineBuffer = nullptr;
351     switch (type)
352     {
353         case PmeSplineDataType::Values:
354             splineBuffer = atc->spline[threadIndex].theta.coefficients[dimIndex];
355             break;
356
357         case PmeSplineDataType::Derivatives:
358             splineBuffer = atc->spline[threadIndex].dtheta.coefficients[dimIndex];
359             break;
360
361         default: GMX_THROW(InternalError("Unknown spline data type"));
362     }
363     return splineBuffer;
364 }
365
366 //! PME solving
367 void pmePerformSolve(const gmx_pme_t*  pme,
368                      CodePath          mode,
369                      PmeSolveAlgorithm method,
370                      real              cellVolume,
371                      GridOrdering      gridOrdering,
372                      bool              computeEnergyAndVirial)
373 {
374     t_complex*   h_grid              = pmeGetComplexGridInternal(pme);
375     const bool   useLorentzBerthelot = false;
376     const size_t threadIndex         = 0;
377     switch (mode)
378     {
379         case CodePath::CPU:
380             if (gridOrdering != GridOrdering::YZX)
381             {
382                 GMX_THROW(InternalError("Test not implemented for this mode"));
383             }
384             switch (method)
385             {
386                 case PmeSolveAlgorithm::Coulomb:
387                     solve_pme_yzx(pme, h_grid, cellVolume, computeEnergyAndVirial, pme->nthread, threadIndex);
388                     break;
389
390                 case PmeSolveAlgorithm::LennardJones:
391                     solve_pme_lj_yzx(pme, &h_grid, useLorentzBerthelot, cellVolume,
392                                      computeEnergyAndVirial, pme->nthread, threadIndex);
393                     break;
394
395                 default: GMX_THROW(InternalError("Test not implemented for this mode"));
396             }
397             break;
398
399         case CodePath::GPU:
400             switch (method)
401             {
402                 case PmeSolveAlgorithm::Coulomb:
403                     pme_gpu_solve(pme->gpu, h_grid, gridOrdering, computeEnergyAndVirial);
404                     break;
405
406                 default: GMX_THROW(InternalError("Test not implemented for this mode"));
407             }
408             break;
409
410         default: GMX_THROW(InternalError("Test not implemented for this mode"));
411     }
412 }
413
414 //! PME force gathering
415 void pmePerformGather(gmx_pme_t* pme, CodePath mode, ForcesVector& forces)
416 {
417     PmeAtomComm* atc       = &(pme->atc[0]);
418     const index  atomCount = atc->numAtoms();
419     GMX_RELEASE_ASSERT(forces.ssize() == atomCount, "Invalid force buffer size");
420     const real   scale       = 1.0;
421     const size_t threadIndex = 0;
422     const size_t gridIndex   = 0;
423     real*        pmegrid     = pme->pmegrid[gridIndex].grid.grid;
424     real*        fftgrid     = pme->fftgrid[gridIndex];
425
426     switch (mode)
427     {
428         case CodePath::CPU:
429             atc->f = forces;
430             if (atc->nthread == 1)
431             {
432                 // something which is normally done in serial spline computation (make_thread_local_ind())
433                 atc->spline[threadIndex].n = atomCount;
434             }
435             copy_fftgrid_to_pmegrid(pme, fftgrid, pmegrid, gridIndex, pme->nthread, threadIndex);
436             unwrap_periodic_pmegrid(pme, pmegrid);
437             gather_f_bsplines(pme, pmegrid, true, atc, &atc->spline[threadIndex], scale);
438             break;
439
440         case CodePath::GPU:
441         {
442             // Variable initialization needs a non-switch scope
443             const bool computeEnergyAndVirial = false;
444             PmeOutput  output                 = pme_gpu_getOutput(*pme, computeEnergyAndVirial);
445             GMX_ASSERT(forces.size() == output.forces_.size(),
446                        "Size of force buffers did not match");
447             pme_gpu_gather(pme->gpu, reinterpret_cast<float*>(fftgrid));
448             std::copy(std::begin(output.forces_), std::end(output.forces_), std::begin(forces));
449         }
450         break;
451
452         default: GMX_THROW(InternalError("Test not implemented for this mode"));
453     }
454 }
455
456 //! PME test finalization before fetching the outputs
457 void pmeFinalizeTest(const gmx_pme_t* pme, CodePath mode)
458 {
459     switch (mode)
460     {
461         case CodePath::CPU: break;
462
463         case CodePath::GPU: pme_gpu_synchronize(pme->gpu); break;
464
465         default: GMX_THROW(InternalError("Test not implemented for this mode"));
466     }
467 }
468
469 //! A binary enum for spline data layout transformation
470 enum class PmeLayoutTransform
471 {
472     GpuToHost,
473     HostToGpu
474 };
475
476 /*! \brief Gets a unique index to an element in a spline parameter buffer.
477  *
478  * These theta/dtheta buffers are laid out for GPU spread/gather
479  * kernels. The index is wrt the execution block, in range(0,
480  * atomsPerBlock * order * DIM).
481  *
482  * This is a wrapper, only used in unit tests.
483  * \param[in] order            PME order
484  * \param[in] splineIndex      Spline contribution index (from 0 to \p order - 1)
485  * \param[in] dimIndex         Dimension index (from 0 to 2)
486  * \param[in] atomIndex        Atom index wrt the block.
487  * \param[in] atomsPerWarp     Number of atoms processed by a warp.
488  *
489  * \returns Index into theta or dtheta array using GPU layout.
490  */
491 static int getSplineParamFullIndex(int order, int splineIndex, int dimIndex, int atomIndex, int atomsPerWarp)
492 {
493     if (order != c_pmeGpuOrder)
494     {
495         throw order;
496     }
497     constexpr int fixedOrder = c_pmeGpuOrder;
498     GMX_UNUSED_VALUE(fixedOrder);
499
500     const int atomWarpIndex = atomIndex % atomsPerWarp;
501     const int warpIndex     = atomIndex / atomsPerWarp;
502     int       indexBase, result;
503     switch (atomsPerWarp)
504     {
505         case 1:
506             indexBase = getSplineParamIndexBase<fixedOrder, 1>(warpIndex, atomWarpIndex);
507             result    = getSplineParamIndex<fixedOrder, 1>(indexBase, dimIndex, splineIndex);
508             break;
509
510         case 2:
511             indexBase = getSplineParamIndexBase<fixedOrder, 2>(warpIndex, atomWarpIndex);
512             result    = getSplineParamIndex<fixedOrder, 2>(indexBase, dimIndex, splineIndex);
513             break;
514
515         case 4:
516             indexBase = getSplineParamIndexBase<fixedOrder, 4>(warpIndex, atomWarpIndex);
517             result    = getSplineParamIndex<fixedOrder, 4>(indexBase, dimIndex, splineIndex);
518             break;
519
520         case 8:
521             indexBase = getSplineParamIndexBase<fixedOrder, 8>(warpIndex, atomWarpIndex);
522             result    = getSplineParamIndex<fixedOrder, 8>(indexBase, dimIndex, splineIndex);
523             break;
524
525         default:
526             GMX_THROW(NotImplementedError(
527                     formatString("Test function call not unrolled for atomsPerWarp = %d in "
528                                  "getSplineParamFullIndex",
529                                  atomsPerWarp)));
530     }
531     return result;
532 }
533
534 /*!\brief Return the number of atoms per warp */
535 static int pme_gpu_get_atoms_per_warp(const PmeGpu* pmeGpu)
536 {
537     const int order = pmeGpu->common->pme_order;
538     const int threadsPerAtom =
539             (pmeGpu->settings.threadsPerAtom == ThreadsPerAtom::Order ? order : order * order);
540     return pmeGpu->programHandle_->warpSize() / threadsPerAtom;
541 }
542
543 /*! \brief Rearranges the atom spline data between the GPU and host layouts.
544  * Only used for test purposes so far, likely to be horribly slow.
545  *
546  * \param[in]  pmeGpu     The PME GPU structure.
547  * \param[out] atc        The PME CPU atom data structure (with a single-threaded layout).
548  * \param[in]  type       The spline data type (values or derivatives).
549  * \param[in]  dimIndex   Dimension index.
550  * \param[in]  transform  Layout transform type
551  */
552 static void pme_gpu_transform_spline_atom_data(const PmeGpu*      pmeGpu,
553                                                const PmeAtomComm* atc,
554                                                PmeSplineDataType  type,
555                                                int                dimIndex,
556                                                PmeLayoutTransform transform)
557 {
558     // The GPU atom spline data is laid out in a different way currently than the CPU one.
559     // This function converts the data from GPU to CPU layout (in the host memory).
560     // It is only intended for testing purposes so far.
561     // Ideally we should use similar layouts on CPU and GPU if we care about mixed modes and their
562     // performance (e.g. spreading on GPU, gathering on CPU).
563     GMX_RELEASE_ASSERT(atc->nthread == 1, "Only the serial PME data layout is supported");
564     const uintmax_t threadIndex  = 0;
565     const auto      atomCount    = atc->numAtoms();
566     const auto      atomsPerWarp = pme_gpu_get_atoms_per_warp(pmeGpu);
567     const auto      pmeOrder     = pmeGpu->common->pme_order;
568     GMX_ASSERT(pmeOrder == c_pmeGpuOrder, "Only PME order 4 is implemented");
569
570     real*  cpuSplineBuffer;
571     float* h_splineBuffer;
572     switch (type)
573     {
574         case PmeSplineDataType::Values:
575             cpuSplineBuffer = atc->spline[threadIndex].theta.coefficients[dimIndex];
576             h_splineBuffer  = pmeGpu->staging.h_theta;
577             break;
578
579         case PmeSplineDataType::Derivatives:
580             cpuSplineBuffer = atc->spline[threadIndex].dtheta.coefficients[dimIndex];
581             h_splineBuffer  = pmeGpu->staging.h_dtheta;
582             break;
583
584         default: GMX_THROW(InternalError("Unknown spline data type"));
585     }
586
587     for (auto atomIndex = 0; atomIndex < atomCount; atomIndex++)
588     {
589         for (auto orderIndex = 0; orderIndex < pmeOrder; orderIndex++)
590         {
591             const auto gpuValueIndex =
592                     getSplineParamFullIndex(pmeOrder, orderIndex, dimIndex, atomIndex, atomsPerWarp);
593             const auto cpuValueIndex = atomIndex * pmeOrder + orderIndex;
594             GMX_ASSERT(cpuValueIndex < atomCount * pmeOrder,
595                        "Atom spline data index out of bounds (while transforming GPU data layout "
596                        "for host)");
597             switch (transform)
598             {
599                 case PmeLayoutTransform::GpuToHost:
600                     cpuSplineBuffer[cpuValueIndex] = h_splineBuffer[gpuValueIndex];
601                     break;
602
603                 case PmeLayoutTransform::HostToGpu:
604                     h_splineBuffer[gpuValueIndex] = cpuSplineBuffer[cpuValueIndex];
605                     break;
606
607                 default: GMX_THROW(InternalError("Unknown layout transform"));
608             }
609         }
610     }
611 }
612
613 //! Setting atom spline values/derivatives to be used in spread/gather
614 void pmeSetSplineData(const gmx_pme_t*             pme,
615                       CodePath                     mode,
616                       const SplineParamsDimVector& splineValues,
617                       PmeSplineDataType            type,
618                       int                          dimIndex)
619 {
620     const PmeAtomComm* atc       = &(pme->atc[0]);
621     const index        atomCount = atc->numAtoms();
622     const index        pmeOrder  = pme->pme_order;
623     const index        dimSize   = pmeOrder * atomCount;
624     GMX_RELEASE_ASSERT(dimSize == splineValues.ssize(), "Mismatch in spline data");
625     real* splineBuffer = pmeGetSplineDataInternal(pme, type, dimIndex);
626
627     switch (mode)
628     {
629         case CodePath::CPU:
630             std::copy(splineValues.begin(), splineValues.end(), splineBuffer);
631             break;
632
633         case CodePath::GPU:
634             std::copy(splineValues.begin(), splineValues.end(), splineBuffer);
635             pme_gpu_transform_spline_atom_data(pme->gpu, atc, type, dimIndex, PmeLayoutTransform::HostToGpu);
636             break;
637
638         default: GMX_THROW(InternalError("Test not implemented for this mode"));
639     }
640 }
641
642 //! Setting gridline indices to be used in spread/gather
643 void pmeSetGridLineIndices(gmx_pme_t* pme, CodePath mode, const GridLineIndicesVector& gridLineIndices)
644 {
645     PmeAtomComm* atc       = &(pme->atc[0]);
646     const index  atomCount = atc->numAtoms();
647     GMX_RELEASE_ASSERT(atomCount == gridLineIndices.ssize(), "Mismatch in gridline indices size");
648
649     IVec paddedGridSizeUnused, gridSize(0, 0, 0);
650     pmeGetRealGridSizesInternal(pme, mode, gridSize, paddedGridSizeUnused);
651
652     for (const auto& index : gridLineIndices)
653     {
654         for (int i = 0; i < DIM; i++)
655         {
656             GMX_RELEASE_ASSERT((0 <= index[i]) && (index[i] < gridSize[i]),
657                                "Invalid gridline index");
658         }
659     }
660
661     switch (mode)
662     {
663         case CodePath::GPU:
664             memcpy(pme_gpu_staging(pme->gpu).h_gridlineIndices, gridLineIndices.data(),
665                    atomCount * sizeof(gridLineIndices[0]));
666             break;
667
668         case CodePath::CPU:
669             atc->idx.resize(gridLineIndices.size());
670             std::copy(gridLineIndices.begin(), gridLineIndices.end(), atc->idx.begin());
671             break;
672         default: GMX_THROW(InternalError("Test not implemented for this mode"));
673     }
674 }
675
676 //! Getting plain index into the complex 3d grid
677 inline size_t pmeGetGridPlainIndexInternal(const IVec& index, const IVec& paddedGridSize, GridOrdering gridOrdering)
678 {
679     size_t result;
680     switch (gridOrdering)
681     {
682         case GridOrdering::YZX:
683             result = (index[YY] * paddedGridSize[ZZ] + index[ZZ]) * paddedGridSize[XX] + index[XX];
684             break;
685
686         case GridOrdering::XYZ:
687             result = (index[XX] * paddedGridSize[YY] + index[YY]) * paddedGridSize[ZZ] + index[ZZ];
688             break;
689
690         default: GMX_THROW(InternalError("Test not implemented for this mode"));
691     }
692     return result;
693 }
694
695 //! Setting real or complex grid
696 template<typename ValueType>
697 static void pmeSetGridInternal(const gmx_pme_t*                        pme,
698                                CodePath                                mode,
699                                GridOrdering                            gridOrdering,
700                                const SparseGridValuesInput<ValueType>& gridValues)
701 {
702     IVec       gridSize(0, 0, 0), paddedGridSize(0, 0, 0);
703     ValueType* grid;
704     pmeGetGridAndSizesInternal<ValueType>(pme, mode, grid, gridSize, paddedGridSize);
705
706     switch (mode)
707     {
708         case CodePath::GPU: // intentional absence of break, the grid will be copied from the host buffer in testing mode
709         case CodePath::CPU:
710             std::memset(grid, 0,
711                         paddedGridSize[XX] * paddedGridSize[YY] * paddedGridSize[ZZ] * sizeof(ValueType));
712             for (const auto& gridValue : gridValues)
713             {
714                 for (int i = 0; i < DIM; i++)
715                 {
716                     GMX_RELEASE_ASSERT((0 <= gridValue.first[i]) && (gridValue.first[i] < gridSize[i]),
717                                        "Invalid grid value index");
718                 }
719                 const size_t gridValueIndex =
720                         pmeGetGridPlainIndexInternal(gridValue.first, paddedGridSize, gridOrdering);
721                 grid[gridValueIndex] = gridValue.second;
722             }
723             break;
724
725         default: GMX_THROW(InternalError("Test not implemented for this mode"));
726     }
727 }
728
729 //! Setting real grid to be used in gather
730 void pmeSetRealGrid(const gmx_pme_t* pme, CodePath mode, const SparseRealGridValuesInput& gridValues)
731 {
732     pmeSetGridInternal<real>(pme, mode, GridOrdering::XYZ, gridValues);
733 }
734
735 //! Setting complex grid to be used in solve
736 void pmeSetComplexGrid(const gmx_pme_t*                    pme,
737                        CodePath                            mode,
738                        GridOrdering                        gridOrdering,
739                        const SparseComplexGridValuesInput& gridValues)
740 {
741     pmeSetGridInternal<t_complex>(pme, mode, gridOrdering, gridValues);
742 }
743
744 //! Getting the single dimension's spline values or derivatives
745 SplineParamsDimVector pmeGetSplineData(const gmx_pme_t* pme, CodePath mode, PmeSplineDataType type, int dimIndex)
746 {
747     GMX_RELEASE_ASSERT(pme != nullptr, "PME data is not initialized");
748     const PmeAtomComm* atc       = &(pme->atc[0]);
749     const size_t       atomCount = atc->numAtoms();
750     const size_t       pmeOrder  = pme->pme_order;
751     const size_t       dimSize   = pmeOrder * atomCount;
752
753     real*                 sourceBuffer = pmeGetSplineDataInternal(pme, type, dimIndex);
754     SplineParamsDimVector result;
755     switch (mode)
756     {
757         case CodePath::GPU:
758             pme_gpu_transform_spline_atom_data(pme->gpu, atc, type, dimIndex, PmeLayoutTransform::GpuToHost);
759             result = arrayRefFromArray(sourceBuffer, dimSize);
760             break;
761
762         case CodePath::CPU: result = arrayRefFromArray(sourceBuffer, dimSize); break;
763
764         default: GMX_THROW(InternalError("Test not implemented for this mode"));
765     }
766     return result;
767 }
768
769 //! Getting the gridline indices
770 GridLineIndicesVector pmeGetGridlineIndices(const gmx_pme_t* pme, CodePath mode)
771 {
772     GMX_RELEASE_ASSERT(pme != nullptr, "PME data is not initialized");
773     const PmeAtomComm* atc       = &(pme->atc[0]);
774     const size_t       atomCount = atc->numAtoms();
775
776     GridLineIndicesVector gridLineIndices;
777     switch (mode)
778     {
779         case CodePath::GPU:
780             gridLineIndices = arrayRefFromArray(
781                     reinterpret_cast<IVec*>(pme_gpu_staging(pme->gpu).h_gridlineIndices), atomCount);
782             break;
783
784         case CodePath::CPU: gridLineIndices = atc->idx; break;
785
786         default: GMX_THROW(InternalError("Test not implemented for this mode"));
787     }
788     return gridLineIndices;
789 }
790
791 //! Getting real or complex grid - only non zero values
792 template<typename ValueType>
793 static SparseGridValuesOutput<ValueType> pmeGetGridInternal(const gmx_pme_t* pme,
794                                                             CodePath         mode,
795                                                             GridOrdering     gridOrdering)
796 {
797     IVec       gridSize(0, 0, 0), paddedGridSize(0, 0, 0);
798     ValueType* grid;
799     pmeGetGridAndSizesInternal<ValueType>(pme, mode, grid, gridSize, paddedGridSize);
800     SparseGridValuesOutput<ValueType> gridValues;
801     switch (mode)
802     {
803         case CodePath::GPU: // intentional absence of break
804         case CodePath::CPU:
805             gridValues.clear();
806             for (int ix = 0; ix < gridSize[XX]; ix++)
807             {
808                 for (int iy = 0; iy < gridSize[YY]; iy++)
809                 {
810                     for (int iz = 0; iz < gridSize[ZZ]; iz++)
811                     {
812                         IVec         temp(ix, iy, iz);
813                         const size_t gridValueIndex =
814                                 pmeGetGridPlainIndexInternal(temp, paddedGridSize, gridOrdering);
815                         const ValueType value = grid[gridValueIndex];
816                         if (value != ValueType{})
817                         {
818                             auto key        = formatString("Cell %d %d %d", ix, iy, iz);
819                             gridValues[key] = value;
820                         }
821                     }
822                 }
823             }
824             break;
825
826         default: GMX_THROW(InternalError("Test not implemented for this mode"));
827     }
828     return gridValues;
829 }
830
831 //! Getting the real grid (spreading output of pmePerformSplineAndSpread())
832 SparseRealGridValuesOutput pmeGetRealGrid(const gmx_pme_t* pme, CodePath mode)
833 {
834     return pmeGetGridInternal<real>(pme, mode, GridOrdering::XYZ);
835 }
836
837 //! Getting the complex grid output of pmePerformSolve()
838 SparseComplexGridValuesOutput pmeGetComplexGrid(const gmx_pme_t* pme, CodePath mode, GridOrdering gridOrdering)
839 {
840     return pmeGetGridInternal<t_complex>(pme, mode, gridOrdering);
841 }
842
843 //! Getting the reciprocal energy and virial
844 PmeOutput pmeGetReciprocalEnergyAndVirial(const gmx_pme_t* pme, CodePath mode, PmeSolveAlgorithm method)
845 {
846     PmeOutput output;
847     switch (mode)
848     {
849         case CodePath::CPU:
850             switch (method)
851             {
852                 case PmeSolveAlgorithm::Coulomb:
853                     get_pme_ener_vir_q(pme->solve_work, pme->nthread, &output);
854                     break;
855
856                 case PmeSolveAlgorithm::LennardJones:
857                     get_pme_ener_vir_lj(pme->solve_work, pme->nthread, &output);
858                     break;
859
860                 default: GMX_THROW(InternalError("Test not implemented for this mode"));
861             }
862             break;
863         case CodePath::GPU:
864             switch (method)
865             {
866                 case PmeSolveAlgorithm::Coulomb: pme_gpu_getEnergyAndVirial(*pme, &output); break;
867
868                 default: GMX_THROW(InternalError("Test not implemented for this mode"));
869             }
870             break;
871
872         default: GMX_THROW(InternalError("Test not implemented for this mode"));
873     }
874     return output;
875 }
876
877 } // namespace test
878 } // namespace gmx