ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
p3m_gpu_cuda.cu
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2026 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/**
21 * @file
22 *
23 * P3M electrostatics on GPU.
24 *
25 * The corresponding header file is @ref p3m_gpu_cuda.cuh.
26 */
27
28#include <config/config.hpp>
29
30#ifdef ESPRESSO_P3M
31
32#define ESPRESSO_P3M_GPU_FLOAT
33// #define ESPRESSO_P3M_GPU_REAL_DOUBLE
34
35#ifdef ESPRESSO_P3M_GPU_FLOAT
36#define REAL_TYPE float
37#define FFT_TYPE_COMPLEX cufftComplex
38#define FFT_FORW_FFT cufftExecR2C
39#define FFT_BACK_FFT cufftExecC2R
40#define FFT_PLAN_FORW_FLAG CUFFT_R2C
41#define FFT_PLAN_BACK_FLAG CUFFT_C2R
42#endif
43
44#ifdef ESPRESSO_P3M_GPU_REAL_DOUBLE
45#define REAL_TYPE double
46#define FFT_TYPE_COMPLEX cufftDoubleComplex
47#define FFT_FORW_FFT cufftExecD2Z
48#define FFT_BACK_FFT cufftExecZ2D
49#define FFT_PLAN_FORW_FLAG CUFFT_D2Z
50#define FFT_PLAN_BACK_FLAG CUFFT_Z2D
51#endif
52
54
55#include "cuda/utils.cuh"
56#include "p3m/math.hpp"
57#include "system/System.hpp"
58
60
63#include <utils/math/sqr.hpp>
64
65#include <cuda.h>
66#include <cufft.h>
67
68#include <algorithm>
69#include <cassert>
70#include <cstddef>
71#include <limits>
72#include <numbers>
73#include <stdexcept>
74#include <tuple>
75
76#if defined(OMPI_MPI_H) || defined(_MPI_H)
77#error CU-file includes mpi.h! This should not happen!
78#endif
79
80using Utils::int_pow;
81using Utils::sqr;
82
83struct P3MGpuData {
84 /** Charge mesh */
86 /** Force meshes */
90 /** Influence Function */
92 /** Charge assignment order */
93 int cao;
94 /** Total number of mesh points (including padding) */
96 /** Ewald parameter */
98 /** Number of particles */
99 unsigned int n_part; // oddity: size_t causes UB with GCC 11.4 in Debug mode
100 /** Box size */
102 /** Mesh dimensions */
103 int mesh[3];
104 /** Padded size */
106 /** Inverse mesh spacing */
108 /** Position shift */
110};
111
113 /** Forward FFT plan */
114 cufftHandle forw_plan;
115 /** Backward FFT plan */
116 cufftHandle back_plan;
117};
118
123
125
127 auto const free_device_pointer = [](auto *&ptr) {
128 if (ptr != nullptr) {
129 cuda_safe_mem(cudaFree(reinterpret_cast<void *>(ptr)));
130 ptr = nullptr;
131 }
132 };
133 free_device_pointer(p3m_gpu_data.charge_mesh);
134 free_device_pointer(p3m_gpu_data.force_mesh_x);
135 free_device_pointer(p3m_gpu_data.force_mesh_y);
136 free_device_pointer(p3m_gpu_data.force_mesh_z);
137 free_device_pointer(p3m_gpu_data.G_hat);
138 cufftDestroy(p3m_fft.forw_plan);
139 cufftDestroy(p3m_fft.back_plan);
140 is_initialized = false;
141 }
142};
143
144static auto p3m_calc_blocks(unsigned int cao, std::size_t n_part) {
145 assert(cao <= 8);
146 auto constexpr max_threads_per_block = 1024u;
147 auto const cao3 = static_cast<unsigned>(Utils::int_pow<3>(cao));
148 auto const parts_per_block = max_threads_per_block / cao3;
149 assert((n_part / parts_per_block) < std::numeric_limits<unsigned>::max());
150 auto n = static_cast<unsigned int>(n_part / parts_per_block);
151 auto n_blocks = ((n_part % parts_per_block) == 0u) ? std::max(1u, n) : n + 1u;
152 return std::make_pair(parts_per_block, static_cast<unsigned>(n_blocks));
153}
154
155dim3 p3m_make_grid(unsigned int n_blocks) {
156 dim3 grid(n_blocks, 1u, 1u);
157 while (grid.x > 65536u) {
158 grid.y++;
159 if ((n_blocks % grid.y) == 0u)
160 grid.x = std::max(1u, n_blocks / grid.y);
161 else
162 grid.x = n_blocks / grid.y + 1u;
163 }
164 return grid;
165}
166
167/**
168 * @brief Calculate the topology of a kernel launch.
169 * Each particle spreads its charge on a cube of size cao, hence cao^3 threads
170 * are needed per particle. Each block may only contain up to 1024 threads.
171 * Compute the maximal number of particles that can be processed by a block,
172 * and the total number of blocks to allocate to process all particles.
173 * Distribute these blocks on a 2D grid, since each accelerator may only
174 * contain up to 2^16 blocks per dimension.
175 */
176static auto p3m_calc_topology(unsigned int cao, std::size_t n_part) {
177 auto const [parts_per_block, n_blocks] = p3m_calc_blocks(cao, n_part);
178 dim3 const block(parts_per_block * cao, cao, cao);
179 dim3 const grid = p3m_make_grid(n_blocks);
180 auto const data_length = std::size_t(3u) *
181 static_cast<std::size_t>(parts_per_block) *
182 static_cast<std::size_t>(cao) * sizeof(REAL_TYPE);
183 return std::make_tuple(block, grid, parts_per_block, data_length);
184}
185
186template <int cao>
187__device__ void static Aliasing_sums_ik(const P3MGpuData p, int NX, int NY,
188 int NZ, REAL_TYPE *Zaehler,
189 REAL_TYPE *Nenner) {
190 REAL_TYPE S1, S2, S3;
191 REAL_TYPE zwi;
192 int MX, MY, MZ;
193 REAL_TYPE NMX, NMY, NMZ;
194 REAL_TYPE NM2;
195 REAL_TYPE TE;
196 REAL_TYPE Leni[3];
197 REAL_TYPE Meshi[3];
198 for (int i = 0; i < 3; ++i) {
199 Leni[i] = 1.0f / p.box[i];
200 Meshi[i] = 1.0f / static_cast<REAL_TYPE>(p.mesh[i]);
201 }
202
203 Zaehler[0] = Zaehler[1] = Zaehler[2] = *Nenner = 0.0;
204
205 for (MX = -P3M_BRILLOUIN; MX <= P3M_BRILLOUIN; MX++) {
206 NMX = static_cast<REAL_TYPE>(((NX > p.mesh[0] / 2) ? NX - p.mesh[0] : NX) +
207 p.mesh[0] * MX);
208 S1 = int_pow<2 * cao>(math::sinc(Meshi[0] * NMX));
209 for (MY = -P3M_BRILLOUIN; MY <= P3M_BRILLOUIN; MY++) {
210 NMY = static_cast<REAL_TYPE>(
211 ((NY > p.mesh[1] / 2) ? NY - p.mesh[1] : NY) + p.mesh[1] * MY);
212 S2 = S1 * int_pow<2 * cao>(math::sinc(Meshi[1] * NMY));
213 for (MZ = -P3M_BRILLOUIN; MZ <= P3M_BRILLOUIN; MZ++) {
214 NMZ = static_cast<REAL_TYPE>(
215 ((NZ > p.mesh[2] / 2) ? NZ - p.mesh[2] : NZ) + p.mesh[2] * MZ);
216 S3 = S2 * int_pow<2 * cao>(math::sinc(Meshi[2] * NMZ));
217
218 NM2 = sqr(NMX * Leni[0]) + sqr(NMY * Leni[1]) + sqr(NMZ * Leni[2]);
219 *Nenner += S3;
220
221 TE = exp(-sqr(std::numbers::pi_v<REAL_TYPE> / (p.alpha)) * NM2);
222 zwi = S3 * TE / NM2;
223 Zaehler[0] += NMX * zwi * Leni[0];
224 Zaehler[1] += NMY * zwi * Leni[1];
225 Zaehler[2] += NMZ * zwi * Leni[2];
226 }
227 }
228 }
229}
230
231/* Calculate influence function */
232template <int cao>
234
235 const auto NX = static_cast<int>(blockDim.x * blockIdx.x + threadIdx.x);
236 const auto NY = static_cast<int>(blockDim.y * blockIdx.y + threadIdx.y);
237 const auto NZ = static_cast<int>(blockDim.z * blockIdx.z + threadIdx.z);
238 REAL_TYPE Dnx, Dny, Dnz;
239 REAL_TYPE Zaehler[3] = {0.0, 0.0, 0.0}, Nenner = 0.0;
240 REAL_TYPE zwi;
241 auto index = 0;
242 REAL_TYPE Leni[3];
243 for (int i = 0; i < 3; ++i) {
244 Leni[i] = REAL_TYPE{1} / p.box[i];
245 }
246
247 if ((NX >= p.mesh[0]) || (NY >= p.mesh[1]) || (NZ >= (p.mesh[2] / 2 + 1)))
248 return;
249
250 index = NX * p.mesh[1] * (p.mesh[2] / 2 + 1) + NY * (p.mesh[2] / 2 + 1) + NZ;
251
252 if (((NX == 0) && (NY == 0) && (NZ == 0)) ||
253 ((NX % (p.mesh[0] / 2) == 0) && (NY % (p.mesh[1] / 2) == 0) &&
254 (NZ % (p.mesh[2] / 2) == 0))) {
255 p.G_hat[index] = 0;
256 } else {
257 Aliasing_sums_ik<cao>(p, NX, NY, NZ, Zaehler, &Nenner);
258
259 Dnx = static_cast<REAL_TYPE>((NX > p.mesh[0] / 2) ? NX - p.mesh[0] : NX);
260 Dny = static_cast<REAL_TYPE>((NY > p.mesh[1] / 2) ? NY - p.mesh[1] : NY);
261 Dnz = static_cast<REAL_TYPE>((NZ > p.mesh[2] / 2) ? NZ - p.mesh[2] : NZ);
262
263 zwi = Dnx * Zaehler[0] * Leni[0] + Dny * Zaehler[1] * Leni[1] +
264 Dnz * Zaehler[2] * Leni[2];
265 zwi /= ((sqr(Dnx * Leni[0]) + sqr(Dny * Leni[1]) + sqr(Dnz * Leni[2])) *
266 sqr(Nenner));
267 p.G_hat[index] = REAL_TYPE{2} * zwi / std::numbers::pi_v<REAL_TYPE>;
268 }
269}
270
271namespace {
272__device__ inline auto linear_index_r(P3MGpuData const &p, int i, int j,
273 int k) {
274 return static_cast<unsigned int>(p.mesh[1] * p.mesh_z_padded * i +
275 p.mesh_z_padded * j + k);
276}
277
278__device__ inline auto linear_index_k(P3MGpuData const &p, int i, int j,
279 int k) {
280 return static_cast<unsigned int>(p.mesh[1] * (p.mesh[2] / 2 + 1) * i +
281 (p.mesh[2] / 2 + 1) * j + k);
282}
283} // namespace
284
285__global__ void apply_diff_op(const P3MGpuData p) {
286 auto const linear_index = linear_index_k(p, static_cast<int>(blockIdx.x),
287 static_cast<int>(blockIdx.y),
288 static_cast<int>(threadIdx.x));
289
290 auto const bidx = static_cast<int>(blockIdx.x);
291 auto const bidy = static_cast<int>(blockIdx.y);
292 auto const nx = (bidx > p.mesh[0] / 2) ? bidx - p.mesh[0] : bidx;
293 auto const ny = (bidy > p.mesh[1] / 2) ? bidy - p.mesh[1] : bidy;
294 auto const nz = static_cast<int>(threadIdx.x);
295
296 const FFT_TYPE_COMPLEX meshw = p.charge_mesh[linear_index];
298 buf.x = REAL_TYPE(-2) * std::numbers::pi_v<REAL_TYPE> * meshw.y;
299 buf.y = REAL_TYPE(+2) * std::numbers::pi_v<REAL_TYPE> * meshw.x;
300
301 p.force_mesh_x[linear_index].x =
302 static_cast<decltype(FFT_TYPE_COMPLEX::x)>(nx) * buf.x / p.box[0];
303 p.force_mesh_x[linear_index].y =
304 static_cast<decltype(FFT_TYPE_COMPLEX::x)>(nx) * buf.y / p.box[0];
305
306 p.force_mesh_y[linear_index].x =
307 static_cast<decltype(FFT_TYPE_COMPLEX::x)>(ny) * buf.x / p.box[1];
308 p.force_mesh_y[linear_index].y =
309 static_cast<decltype(FFT_TYPE_COMPLEX::x)>(ny) * buf.y / p.box[1];
310
311 p.force_mesh_z[linear_index].x =
312 static_cast<decltype(FFT_TYPE_COMPLEX::x)>(nz) * buf.x / p.box[2];
313 p.force_mesh_z[linear_index].y =
314 static_cast<decltype(FFT_TYPE_COMPLEX::x)>(nz) * buf.y / p.box[2];
315}
316
317__device__ inline int wrap_index(const int ind, const int mesh) {
318 if (ind < 0)
319 return ind + mesh;
320 if (ind >= mesh)
321 return ind - mesh;
322 return ind;
323}
324
325__global__ void apply_influence_function(const P3MGpuData p) {
326 auto const linear_index = linear_index_k(p, static_cast<int>(blockIdx.x),
327 static_cast<int>(blockIdx.y),
328 static_cast<int>(threadIdx.x));
329
330 p.charge_mesh[linear_index].x *= p.G_hat[linear_index];
331 p.charge_mesh[linear_index].y *= p.G_hat[linear_index];
332}
333
334template <int cao, bool shared>
335__global__ void assign_charge_kernel(P3MGpuData const params,
336 float const *const __restrict__ part_pos,
337 float const *const __restrict__ part_q,
338 unsigned int const parts_per_block) {
339 auto const part_in_block = threadIdx.x / static_cast<unsigned int>(cao);
340 auto const cao_id_x =
341 threadIdx.x - part_in_block * static_cast<unsigned int>(cao);
342 /* id of the particle */
343 auto const id =
344 parts_per_block * (blockIdx.x * gridDim.y + blockIdx.y) + part_in_block;
345 if (id >= params.n_part)
346 return;
347 /* position relative to the closest grid point */
348 REAL_TYPE m_pos[3];
349 /* index of the nearest mesh point */
350 int nmp_x, nmp_y, nmp_z;
351
352 auto *charge_mesh = (REAL_TYPE *)params.charge_mesh;
353
354 m_pos[0] = part_pos[3 * id + 0] * params.hi[0] - params.pos_shift;
355 m_pos[1] = part_pos[3 * id + 1] * params.hi[1] - params.pos_shift;
356 m_pos[2] = part_pos[3 * id + 2] * params.hi[2] - params.pos_shift;
357
358 nmp_x = static_cast<int>(floorf(m_pos[0] + 0.5f));
359 nmp_y = static_cast<int>(floorf(m_pos[1] + 0.5f));
360 nmp_z = static_cast<int>(floorf(m_pos[2] + 0.5f));
361
362 m_pos[0] -= static_cast<REAL_TYPE>(nmp_x);
363 m_pos[1] -= static_cast<REAL_TYPE>(nmp_y);
364 m_pos[2] -= static_cast<REAL_TYPE>(nmp_z);
365
366 nmp_x = wrap_index(nmp_x + static_cast<int>(cao_id_x), params.mesh[0]);
367 nmp_y = wrap_index(nmp_y + static_cast<int>(threadIdx.y), params.mesh[1]);
368 nmp_z = wrap_index(nmp_z + static_cast<int>(threadIdx.z), params.mesh[2]);
369
370 auto const index = linear_index_r(params, nmp_x, nmp_y, nmp_z);
371
372 extern __shared__ float weights[];
373
374 if (shared) {
375 auto const offset = static_cast<unsigned int>(cao) * part_in_block;
376 if ((threadIdx.y < 3u) && (threadIdx.z == 0u)) {
377 weights[3u * offset + 3u * cao_id_x + threadIdx.y] =
378 Utils::bspline<cao>(static_cast<int>(cao_id_x), m_pos[threadIdx.y]);
379 }
380
381 __syncthreads();
382
383 auto const c = weights[3u * offset + 3u * cao_id_x] *
384 weights[3u * offset + 3u * threadIdx.y + 1u] *
385 weights[3u * offset + 3u * threadIdx.z + 2u] * part_q[id];
386 atomicAdd(&(charge_mesh[index]), REAL_TYPE(c));
387
388 } else {
389 auto const c =
390 Utils::bspline<cao>(static_cast<int>(cao_id_x), m_pos[0]) * part_q[id] *
391 Utils::bspline<cao>(static_cast<int>(threadIdx.y), m_pos[1]) *
392 Utils::bspline<cao>(static_cast<int>(threadIdx.z), m_pos[2]);
393 atomicAdd(&(charge_mesh[index]), REAL_TYPE(c));
394 }
395}
396
397void assign_charges(P3MGpuData const &params,
398 float const *const __restrict__ part_pos,
399 float const *const __restrict__ part_q) {
400 auto const cao = static_cast<unsigned int>(params.cao);
401 auto const [block, grid, parts_per_block, data_length] =
402 p3m_calc_topology(cao, params.n_part);
403 // the shared version only is faster for cao > 2
404 switch (params.cao) {
405 case 1:
406 (assign_charge_kernel<1, false>)<<<grid, block, std::size_t(0u), nullptr>>>(
407 params, part_pos, part_q, parts_per_block);
408 break;
409 case 2:
410 (assign_charge_kernel<2, false>)<<<grid, block, std::size_t(0u), nullptr>>>(
411 params, part_pos, part_q, parts_per_block);
412 break;
413 case 3:
414 (assign_charge_kernel<3, true>)<<<grid, block, data_length, nullptr>>>(
415 params, part_pos, part_q, parts_per_block);
416 break;
417 case 4:
418 (assign_charge_kernel<4, true>)<<<grid, block, data_length, nullptr>>>(
419 params, part_pos, part_q, parts_per_block);
420 break;
421 case 5:
422 (assign_charge_kernel<5, true>)<<<grid, block, data_length, nullptr>>>(
423 params, part_pos, part_q, parts_per_block);
424 break;
425 case 6:
426 (assign_charge_kernel<6, true>)<<<grid, block, data_length, nullptr>>>(
427 params, part_pos, part_q, parts_per_block);
428 break;
429 case 7:
430 (assign_charge_kernel<7, true>)<<<grid, block, data_length, nullptr>>>(
431 params, part_pos, part_q, parts_per_block);
432 break;
433 default:
434 break;
435 }
436 cuda_check_errors_exit(block, grid, "assign_charge", __FILE__, __LINE__);
437}
438
439template <int cao, bool shared>
440__global__ void assign_forces_kernel(P3MGpuData const params,
441 float const *const __restrict__ part_pos,
442 float const *const __restrict__ part_q,
443 float *const __restrict__ part_f,
444 REAL_TYPE prefactor,
445 unsigned int const parts_per_block) {
446 auto const part_in_block = threadIdx.x / static_cast<unsigned int>(cao);
447 auto const cao_id_x =
448 threadIdx.x - part_in_block * static_cast<unsigned int>(cao);
449 /* id of the particle */
450 auto const id =
451 parts_per_block * (blockIdx.x * gridDim.y + blockIdx.y) + part_in_block;
452 if (id >= static_cast<unsigned>(params.n_part))
453 return;
454 /* position relative to the closest grid point */
455 REAL_TYPE m_pos[3];
456 /* index of the nearest mesh point */
457 int nmp_x, nmp_y, nmp_z;
458
459 m_pos[0] = part_pos[3u * id + 0u] * params.hi[0] - params.pos_shift;
460 m_pos[1] = part_pos[3u * id + 1u] * params.hi[1] - params.pos_shift;
461 m_pos[2] = part_pos[3u * id + 2u] * params.hi[2] - params.pos_shift;
462
463 nmp_x = static_cast<int>(floorf(m_pos[0] + REAL_TYPE{0.5}));
464 nmp_y = static_cast<int>(floorf(m_pos[1] + REAL_TYPE{0.5}));
465 nmp_z = static_cast<int>(floorf(m_pos[2] + REAL_TYPE{0.5}));
466
467 m_pos[0] -= static_cast<REAL_TYPE>(nmp_x);
468 m_pos[1] -= static_cast<REAL_TYPE>(nmp_y);
469 m_pos[2] -= static_cast<REAL_TYPE>(nmp_z);
470
471 nmp_x = wrap_index(nmp_x + static_cast<int>(cao_id_x), params.mesh[0]);
472 nmp_y = wrap_index(nmp_y + static_cast<int>(threadIdx.y), params.mesh[1]);
473 nmp_z = wrap_index(nmp_z + static_cast<int>(threadIdx.z), params.mesh[2]);
474
475 auto const index = linear_index_r(params, nmp_x, nmp_y, nmp_z);
476
477 extern __shared__ float weights[];
478
479 REAL_TYPE c = -prefactor * part_q[id];
480 if (shared) {
481 auto const offset = static_cast<unsigned int>(cao) * part_in_block;
482 if ((threadIdx.y < 3u) && (threadIdx.z == 0u)) {
483 weights[3u * offset + 3u * cao_id_x + threadIdx.y] =
484 Utils::bspline<cao>(static_cast<int>(cao_id_x), m_pos[threadIdx.y]);
485 }
486
487 __syncthreads();
488
489 c *= REAL_TYPE(weights[3u * offset + 3u * cao_id_x] *
490 weights[3u * offset + 3u * threadIdx.y + 1u] *
491 weights[3u * offset + 3u * threadIdx.z + 2u]);
492 } else {
493 c *=
494 REAL_TYPE(Utils::bspline<cao>(static_cast<int>(cao_id_x), m_pos[0]) *
495 Utils::bspline<cao>(static_cast<int>(threadIdx.y), m_pos[1]) *
496 Utils::bspline<cao>(static_cast<int>(threadIdx.z), m_pos[2]));
497 }
498
499 const REAL_TYPE *force_mesh_x = (REAL_TYPE *)params.force_mesh_x;
500 const REAL_TYPE *force_mesh_y = (REAL_TYPE *)params.force_mesh_y;
501 const REAL_TYPE *force_mesh_z = (REAL_TYPE *)params.force_mesh_z;
502
503 atomicAdd(&(part_f[3u * id + 0u]), float(c * force_mesh_x[index]));
504 atomicAdd(&(part_f[3u * id + 1u]), float(c * force_mesh_y[index]));
505 atomicAdd(&(part_f[3u * id + 2u]), float(c * force_mesh_z[index]));
506}
507
508void assign_forces(P3MGpuData const &params,
509 float const *const __restrict__ part_pos,
510 float const *const __restrict__ part_q,
511 float *const __restrict__ part_f,
512 REAL_TYPE const prefactor) {
513 auto const cao = static_cast<unsigned int>(params.cao);
514 auto const [block, grid, parts_per_block, data_length] =
515 p3m_calc_topology(cao, params.n_part);
516 // the shared version only is faster for cao > 2
517 switch (params.cao) {
518 case 1:
519 (assign_forces_kernel<1, false>)<<<grid, block, std::size_t(0u), nullptr>>>(
520 params, part_pos, part_q, part_f, prefactor, parts_per_block);
521 break;
522 case 2:
523 (assign_forces_kernel<2, false>)<<<grid, block, std::size_t(0u), nullptr>>>(
524 params, part_pos, part_q, part_f, prefactor, parts_per_block);
525 break;
526 case 3:
527 (assign_forces_kernel<3, true>)<<<grid, block, data_length, nullptr>>>(
528 params, part_pos, part_q, part_f, prefactor, parts_per_block);
529 break;
530 case 4:
531 (assign_forces_kernel<4, true>)<<<grid, block, data_length, nullptr>>>(
532 params, part_pos, part_q, part_f, prefactor, parts_per_block);
533 break;
534 case 5:
535 (assign_forces_kernel<5, true>)<<<grid, block, data_length, nullptr>>>(
536 params, part_pos, part_q, part_f, prefactor, parts_per_block);
537 break;
538 case 6:
539 (assign_forces_kernel<6, true>)<<<grid, block, data_length, nullptr>>>(
540 params, part_pos, part_q, part_f, prefactor, parts_per_block);
541 break;
542 case 7:
543 (assign_forces_kernel<7, true>)<<<grid, block, data_length, nullptr>>>(
544 params, part_pos, part_q, part_f, prefactor, parts_per_block);
545 break;
546 default:
547 break;
548 }
549 cuda_check_errors_exit(block, grid, "assign_forces", __FILE__, __LINE__);
550}
551
552/**
553 * @brief Initialize the internal data structure of the P3M GPU.
554 * Mainly allocation on the device and influence function calculation.
555 * Be advised: this needs `mesh^3*5*sizeof(REAL_TYPE)` of device memory.
556 * We use real to complex FFTs, so the size of the reciprocal mesh
557 * is (cuFFT convention) `Nx * Ny * ( Nz /2 + 1 )`.
558 */
559void p3m_gpu_init(std::shared_ptr<P3MGpuParams> &data, int cao,
560 Utils::Vector3i const &mesh, double alpha,
561 Utils::Vector3d const &box_l, std::size_t n_part) {
562 assert(mesh != Utils::Vector3i::broadcast(-1));
563
564 if (not data) {
565 data = std::make_shared<P3MGpuParams>();
566 }
567
568 auto &p3m_gpu_data = data->p3m_gpu_data;
569 bool do_reinit = false, mesh_changed = false;
570 assert(n_part <= std::numeric_limits<unsigned int>::max());
571 p3m_gpu_data.n_part = static_cast<unsigned>(n_part);
572
573 if (not data->is_initialized or p3m_gpu_data.alpha != alpha) {
574 p3m_gpu_data.alpha = static_cast<REAL_TYPE>(alpha);
575 do_reinit = true;
576 }
577
578 if (not data->is_initialized or p3m_gpu_data.cao != cao) {
579 p3m_gpu_data.cao = cao;
580 // NOLINTNEXTLINE(bugprone-integer-division)
581 p3m_gpu_data.pos_shift = static_cast<REAL_TYPE>((p3m_gpu_data.cao - 1) / 2);
582 do_reinit = true;
583 }
584
585 if (not data->is_initialized or mesh != Utils::Vector3i(p3m_gpu_data.mesh)) {
586 std::ranges::copy(mesh, p3m_gpu_data.mesh);
587 mesh_changed = true;
588 do_reinit = true;
589 }
590
591 if (auto constexpr eps =
592 static_cast<double>(std::numeric_limits<float>::epsilon());
593 not data->is_initialized or
594 (box_l - Utils::Vector3d(p3m_gpu_data.box)).norm() >= eps) {
595 std::ranges::copy(box_l, p3m_gpu_data.box);
596 do_reinit = true;
597 }
598
599 p3m_gpu_data.mesh_z_padded = (mesh[2] / 2 + 1) * 2;
600 p3m_gpu_data.mesh_size = mesh[0] * mesh[1] * p3m_gpu_data.mesh_z_padded;
601
602 for (auto i = 0u; i < 3u; ++i) {
603 p3m_gpu_data.hi[i] =
604 static_cast<REAL_TYPE>(p3m_gpu_data.mesh[i]) / p3m_gpu_data.box[i];
605 }
606
607 if (data->is_initialized and mesh_changed) {
608 data->free_device_memory();
609 data->is_initialized = false;
610 }
611
612 if (not data->is_initialized and p3m_gpu_data.mesh_size > 0) {
613 /* Size of the complex mesh Nx * Ny * ( Nz / 2 + 1 ) */
614 auto const cmesh_size =
615 static_cast<std::size_t>(p3m_gpu_data.mesh[0]) *
616 static_cast<std::size_t>(p3m_gpu_data.mesh[1]) *
617 static_cast<std::size_t>(p3m_gpu_data.mesh[2] / 2 + 1);
618 auto const mesh_len = cmesh_size * sizeof(FFT_TYPE_COMPLEX);
619 cuda_safe_mem(cudaMalloc((void **)&(p3m_gpu_data.charge_mesh), mesh_len));
620 cuda_safe_mem(cudaMalloc((void **)&(p3m_gpu_data.force_mesh_x), mesh_len));
621 cuda_safe_mem(cudaMalloc((void **)&(p3m_gpu_data.force_mesh_y), mesh_len));
622 cuda_safe_mem(cudaMalloc((void **)&(p3m_gpu_data.force_mesh_z), mesh_len));
623 cuda_safe_mem(cudaMalloc((void **)&(p3m_gpu_data.G_hat),
624 cmesh_size * sizeof(REAL_TYPE)));
625
626 {
627#ifdef ESPRESSO_FPE
628 // cuFFT builds device kernels using CUDA-JIT
629 // (https://docs.nvidia.com/cuda/archive/13.1.1/cufft/#plan-initialization-time)
630 // please note this operation is not guaranteed to succeed for all
631 // mesh sizes, and in rare cases, it can send the SIGFPE signal
632 auto const trap_pause = fe_trap::make_shared_pause_scoped();
633#endif
634 if (cufftPlan3d(&(data->p3m_fft.forw_plan), mesh[0], mesh[1], mesh[2],
635 FFT_PLAN_FORW_FLAG) != CUFFT_SUCCESS or
636 cufftPlan3d(&(data->p3m_fft.back_plan), mesh[0], mesh[1], mesh[2],
637 FFT_PLAN_BACK_FLAG) != CUFFT_SUCCESS) {
638 throw std::runtime_error("Unable to create fft plan");
639 }
640 }
641 }
642
643 if ((do_reinit or not data->is_initialized) and p3m_gpu_data.mesh_size > 0) {
644 dim3 grid(1, 1, 1);
645 dim3 block(1, 1, 1);
646 block.x = static_cast<unsigned>(512 / mesh[0] + 1);
647 block.y = static_cast<unsigned>(mesh[1]);
648 block.z = 1;
649 grid.x = static_cast<unsigned>(mesh[0]) / block.x + 1;
650 grid.z = static_cast<unsigned>(mesh[2]) / 2 + 1;
651
652 switch (p3m_gpu_data.cao) {
653 case 1:
654 KERNELCALL(calculate_influence_function_device<1>, grid, block,
655 p3m_gpu_data);
656 break;
657 case 2:
658 KERNELCALL(calculate_influence_function_device<2>, grid, block,
659 p3m_gpu_data);
660 break;
661 case 3:
662 KERNELCALL(calculate_influence_function_device<3>, grid, block,
663 p3m_gpu_data);
664 break;
665 case 4:
666 KERNELCALL(calculate_influence_function_device<4>, grid, block,
667 p3m_gpu_data);
668 break;
669 case 5:
670 KERNELCALL(calculate_influence_function_device<5>, grid, block,
671 p3m_gpu_data);
672 break;
673 case 6:
674 KERNELCALL(calculate_influence_function_device<6>, grid, block,
675 p3m_gpu_data);
676 break;
677 case 7:
678 KERNELCALL(calculate_influence_function_device<7>, grid, block,
679 p3m_gpu_data);
680 break;
681 }
682 }
683 if (p3m_gpu_data.mesh_size > 0)
684 data->is_initialized = true;
685}
686
687/**
688 * \brief The long-range part of the P3M algorithm.
689 */
691 double prefactor, std::size_t n_part) {
692 auto &p3m_gpu_data = data.p3m_gpu_data;
693 p3m_gpu_data.n_part = static_cast<unsigned>(n_part);
694
695 if (n_part == 0u)
696 return;
697
698 auto const positions_device = gpu.get_particle_positions_device();
699 auto const charges_device = gpu.get_particle_charges_device();
700 auto const forces_device = gpu.get_particle_forces_device();
701
702 dim3 gridConv(static_cast<unsigned>(p3m_gpu_data.mesh[0]),
703 static_cast<unsigned>(p3m_gpu_data.mesh[1]), 1u);
704 dim3 threadsConv(static_cast<unsigned>(p3m_gpu_data.mesh[2] / 2 + 1), 1u, 1u);
705
706 auto const volume =
707 Utils::product(Utils::Vector3<REAL_TYPE>(p3m_gpu_data.box));
708 auto const pref = static_cast<REAL_TYPE>(prefactor) / (volume * REAL_TYPE{2});
709
710 cuda_safe_mem(cudaMemset(p3m_gpu_data.charge_mesh, 0,
711 static_cast<std::size_t>(p3m_gpu_data.mesh_size) *
712 sizeof(REAL_TYPE)));
713
714 /* Interpolate the charges to the mesh */
715 assign_charges(p3m_gpu_data, positions_device, charges_device);
716
717 /* Do forward FFT of the charge mesh */
719 (REAL_TYPE *)p3m_gpu_data.charge_mesh,
720 p3m_gpu_data.charge_mesh) != CUFFT_SUCCESS) {
721 throw std::runtime_error("CUFFT error: Forward FFT failed");
722 }
723
724 /* Do convolution */
725 KERNELCALL(apply_influence_function, gridConv, threadsConv, p3m_gpu_data);
726
727 /* Take derivative */
728 KERNELCALL(apply_diff_op, gridConv, threadsConv, p3m_gpu_data);
729
730 /* Transform the components of the electric field back */
731 FFT_BACK_FFT(data.p3m_fft.back_plan, p3m_gpu_data.force_mesh_x,
732 (REAL_TYPE *)p3m_gpu_data.force_mesh_x);
733 FFT_BACK_FFT(data.p3m_fft.back_plan, p3m_gpu_data.force_mesh_y,
734 (REAL_TYPE *)p3m_gpu_data.force_mesh_y);
735 FFT_BACK_FFT(data.p3m_fft.back_plan, p3m_gpu_data.force_mesh_z,
736 (REAL_TYPE *)p3m_gpu_data.force_mesh_z);
737
738 /* Assign the forces from the mesh back to the particles */
739 assign_forces(p3m_gpu_data, positions_device, charges_device, forces_device,
740 pref);
741}
742
743#endif // ESPRESSO_P3M
Particle data communication manager for the GPU.
float * get_particle_charges_device() const
float * get_particle_forces_device() const
float * get_particle_positions_device() const
static DEVICE_QUALIFIER constexpr Vector< T, N > broadcast(typename Base::value_type const &value) noexcept
Create a vector that has all entries set to the same value.
Definition Vector.hpp:131
static std::shared_ptr< scoped_pause > make_shared_pause_scoped()
Generate a shared handle to temporarily disable any currently active exception trap for the lifetime ...
Definition fe_trap.cpp:144
void cuda_check_errors_exit(const dim3 &block, const dim3 &grid, const char *function, const char *file, unsigned int line)
In case of error during a CUDA operation, print the error message and exit.
static double * block(double *p, std::size_t index, std::size_t size)
Definition elc.cpp:175
#define P3M_BRILLOUIN
P3M: Number of Brillouin zones taken into account in the calculation of the optimal influence functio...
Definition math.hpp:38
T product(Vector< T, N > const &v)
Definition Vector.hpp:380
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
Definition sqr.hpp:28
DEVICE_QUALIFIER constexpr T int_pow(T x)
Calculate integer powers.
Definition int_pow.hpp:34
__device__ auto linear_index_k(P3MGpuData const &p, int i, int j, int k)
__device__ auto linear_index_r(P3MGpuData const &p, int i, int j, int k)
DEVICE_QUALIFIER auto sinc(T x)
Calculate the function .
Definition math.hpp:71
__device__ static void Aliasing_sums_ik(const P3MGpuData p, int NX, int NY, int NZ, REAL_TYPE *Zaehler, REAL_TYPE *Nenner)
#define FFT_BACK_FFT
__global__ void assign_charge_kernel(P3MGpuData const params, float const *const __restrict__ part_pos, float const *const __restrict__ part_q, unsigned int const parts_per_block)
void p3m_gpu_add_farfield_force(P3MGpuParams &data, GpuParticleData &gpu, double prefactor, std::size_t n_part)
The long-range part of the P3M algorithm.
__global__ void apply_influence_function(const P3MGpuData p)
void assign_charges(P3MGpuData const &params, float const *const __restrict__ part_pos, float const *const __restrict__ part_q)
#define FFT_PLAN_FORW_FLAG
__global__ void apply_diff_op(const P3MGpuData p)
void p3m_gpu_init(std::shared_ptr< P3MGpuParams > &data, int cao, Utils::Vector3i const &mesh, double alpha, Utils::Vector3d const &box_l, std::size_t n_part)
Initialize the internal data structure of the P3M GPU.
static auto p3m_calc_blocks(unsigned int cao, std::size_t n_part)
#define REAL_TYPE
void assign_forces(P3MGpuData const &params, float const *const __restrict__ part_pos, float const *const __restrict__ part_q, float *const __restrict__ part_f, REAL_TYPE const prefactor)
__global__ void calculate_influence_function_device(const P3MGpuData p)
static auto p3m_calc_topology(unsigned int cao, std::size_t n_part)
Calculate the topology of a kernel launch.
#define FFT_TYPE_COMPLEX
__device__ int wrap_index(const int ind, const int mesh)
#define FFT_PLAN_BACK_FLAG
dim3 p3m_make_grid(unsigned int n_blocks)
__global__ void assign_forces_kernel(P3MGpuData const params, float const *const __restrict__ part_pos, float const *const __restrict__ part_q, float *const __restrict__ part_f, REAL_TYPE prefactor, unsigned int const parts_per_block)
#define FFT_FORW_FFT
int mesh[3]
Mesh dimensions.
REAL_TYPE pos_shift
Position shift.
REAL_TYPE * G_hat
Influence Function.
FFT_TYPE_COMPLEX * force_mesh_x
Force meshes.
int mesh_z_padded
Padded size.
int mesh_size
Total number of mesh points (including padding)
int cao
Charge assignment order.
unsigned int n_part
Number of particles.
FFT_TYPE_COMPLEX * force_mesh_z
REAL_TYPE hi[3]
Inverse mesh spacing.
FFT_TYPE_COMPLEX * charge_mesh
Charge mesh.
REAL_TYPE box[3]
Box size.
FFT_TYPE_COMPLEX * force_mesh_y
REAL_TYPE alpha
Ewald parameter.
cufftHandle forw_plan
Forward FFT plan.
cufftHandle back_plan
Backward FFT plan.
void free_device_memory()
P3MGpuData p3m_gpu_data
P3MGpuFftPlan p3m_fft
#define cuda_safe_mem(a)
Definition utils.cuh:73
#define KERNELCALL(_function, _grid, _block,...)
Definition utils.cuh:79