core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
particle_grid.hpp
Go to the documentation of this file.
1// core — generic trilinear particle<->grid interpolation (the P2G / G2P primitive for CFD-DEM).
2//
3// Physics-free: gather a cell-centred grid field to particle locations (grid->particle, G2P) and
4// scatter a per-particle scalar onto the grid with the transpose weights (particle->grid, P2G, via
5// atomic_add so overlapping particles accumulate). Both are Kokkos parallel_fors over the device
6// views the caller supplies, so the coupling never leaves the device.
7//
8// Layout contract (matches flow's fields): the field is a FLAT x-fastest buffer of padded extents
9// (ex,ey,ez) = (nx+2g, ny+2g, nz+2g); a cell-centred inner sample (i,j,k) in [0,nx)x[0,ny)x[0,nz)
10// lives at flat index (i+g) + (j+g)*ex + (k+g)*ex*ey and represents the value at physical position
11// origin + (i+0.5)*h. With ghost width g>=1 the 8-point trilinear stencil of any particle inside
12// [origin, origin+L] stays in bounds (the half-cell overhang lands on a ghost cell); indices are
13// clamped defensively regardless.
14//
15// Header-only, Kokkos required. Templated on the view types so it composes with dem's float particle
16// SoA and flow's double grid fields (interpolation arithmetic is done in double).
17#ifndef PECLET_CORE_INTERP_PARTICLE_GRID_HPP
18#define PECLET_CORE_INTERP_PARTICLE_GRID_HPP
19
20#include <Kokkos_Core.hpp>
21
23
24// Origin, inverse spacing, padded extents and ghost width of a cell-centred grid field.
25struct GridMap {
26 double ox, oy, oz; // physical origin (low corner of cell (0,0,0))
27 double idx, idy, idz; // inverse spacing 1/h per axis
28 int ex, ey, ez; // PADDED extents (inner + 2*ghost)
29 int g; // ghost width
30};
31
32namespace detail {
33// Cell-centred trilinear stencil at physical coordinate p along one axis: base inner index i0 and
34// weight w in [0,1) so value = (1-w)*f[i0] + w*f[i0+1]. si = (p-origin)*inv - 0.5 (cell centres are
35// half a cell in from the low corner).
36KOKKOS_INLINE_FUNCTION void axisStencil(double p, double origin, double inv, int nInner, int& i0,
37 double& w) {
38 const double si = (p - origin) * inv - 0.5;
39 double fi = Kokkos::floor(si);
40 i0 = (int)fi;
41 w = si - fi;
42 if (i0 < -1) {
43 i0 = -1;
44 w = 0.0;
45 }
46 if (i0 > nInner - 1) {
47 i0 = nInner - 1;
48 w = 1.0;
49 }
50}
51} // namespace detail
52
53// G2P: out(p) = trilinear interpolation of the cell-centred field at pos(p). PosView is (N,3) float
54// or double; FieldView is a flat View<T*>; OutView is a per-particle View<Tout*>.
55template <class PosView, class FieldView, class OutView>
57 using Exec = Kokkos::DefaultExecutionSpace;
58 const int ex = m.ex, ey = m.ey, ez = m.ez, g = m.g;
59 const int nx = ex - 2 * g, ny = ey - 2 * g, nz = ez - 2 * g;
60 Kokkos::parallel_for(
61 "peclet::core::interp::gather", Kokkos::RangePolicy<Exec>(0, nParticles), KOKKOS_LAMBDA(int p) {
62 int i0, j0, k0;
63 double wx, wy, wz;
64 detail::axisStencil((double)pos(p, 0), m.ox, m.idx, nx, i0, wx);
65 detail::axisStencil((double)pos(p, 1), m.oy, m.idy, ny, j0, wy);
66 detail::axisStencil((double)pos(p, 2), m.oz, m.idz, nz, k0, wz);
67 const long sx = 1, sy = ex, sz = (long)ex * ey;
68 const long b = (long)(i0 + g) + (long)(j0 + g) * sy + (long)(k0 + g) * sz;
69 auto F = [&](long o) { return (double)field(b + o); };
70 const double c00 = F(0) * (1 - wx) + F(sx) * wx;
71 const double c10 = F(sy) * (1 - wx) + F(sy + sx) * wx;
72 const double c01 = F(sz) * (1 - wx) + F(sz + sx) * wx;
73 const double c11 = F(sz + sy) * (1 - wx) + F(sz + sy + sx) * wx;
74 const double c0 = c00 * (1 - wy) + c10 * wy;
75 const double c1 = c01 * (1 - wy) + c11 * wy;
76 out(p) = c0 * (1 - wz) + c1 * wz;
77 });
78}
79
80// P2G: atomic_add q(p) onto the 8 surrounding cell-centred field cells with the SAME trilinear
81// weights (the transpose of gather — Σ over particles conserves the deposited quantity). field must
82// be pre-zeroed by the caller. Atomics => run-to-run order dependence (results tolerance-, not
83// bit-exact).
84template <class PosView, class QView, class FieldView>
86 using Exec = Kokkos::DefaultExecutionSpace;
87 const int ex = m.ex, ey = m.ey, ez = m.ez, g = m.g;
88 const int nx = ex - 2 * g, ny = ey - 2 * g, nz = ez - 2 * g;
89 using T = typename FieldView::value_type;
90 Kokkos::parallel_for(
91 "peclet::core::interp::scatter", Kokkos::RangePolicy<Exec>(0, nParticles),
92 KOKKOS_LAMBDA(int p) {
93 int i0, j0, k0;
94 double wx, wy, wz;
95 detail::axisStencil((double)pos(p, 0), m.ox, m.idx, nx, i0, wx);
96 detail::axisStencil((double)pos(p, 1), m.oy, m.idy, ny, j0, wy);
97 detail::axisStencil((double)pos(p, 2), m.oz, m.idz, nz, k0, wz);
98 const long sx = 1, sy = ex, sz = (long)ex * ey;
99 const long b = (long)(i0 + g) + (long)(j0 + g) * sy + (long)(k0 + g) * sz;
100 const double qv = (double)q(p);
101 const double w[2] = {1.0 - wx, wx}, wj[2] = {1.0 - wy, wy}, wk[2] = {1.0 - wz, wz};
102 for (int dk = 0; dk < 2; ++dk)
103 for (int dj = 0; dj < 2; ++dj)
104 for (int di = 0; di < 2; ++di) {
105 const long o = b + di * sx + dj * sy + dk * sz;
106 Kokkos::atomic_add(&field(o), (T)(qv * w[di] * wj[dj] * wk[dk]));
107 }
108 });
109}
110
111} // namespace peclet::core::interp
112
113#endif // PECLET_CORE_INTERP_PARTICLE_GRID_HPP
void axisStencil(double p, double origin, double inv, int nInner, int &i0, double &w)
void trilinearScatterAtomic(int nParticles, PosView pos, QView q, FieldView field, GridMap m)
void trilinearGather(int nParticles, PosView pos, FieldView field, OutView out, GridMap m)
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26