IPPL API Reference
Independent Parallel Particle Layer C++ API
Loading...
Searching...
No Matches
FieldBufferOps.hpp
1//
2// Shared pack / unpack / solver_send / solver_recv helpers for Field-like
3// communication patterns. Originally inlined in FFTOpenPoissonSolver.hpp and
4// promoted here so other utilities (e.g. mirrorField) can reuse the exact same
5// device-parallel, CUDA-aware-MPI-compatible primitives.
6//
7// All functions operate on Kokkos views and ippl::detail::FieldBufferData
8// buffers whose memory space is inferred from the view, so data stays on the
9// device when the underlying MPI is GPU-aware.
10//
11#ifndef IPPL_FIELD_BUFFER_OPS_HPP
12#define IPPL_FIELD_BUFFER_OPS_HPP
13
14#include <vector>
15
16#include "Types/Vector.h"
17
18#include "Communicate/Archive.h"
19#include "Field/HaloCells.h"
20#include "Index/NDIndex.h"
21
22namespace ippl {
23 namespace detail {
24 // Access a view that either contains a scalar, vector, or matrix field
25 // so that the correct element is selected at compile time. Used by
26 // unpack_impl to support rank-0 (scalar), rank-1 (vector), and rank-2
27 // (matrix) field types with a single kernel.
28 template <int tensorRank, typename View, unsigned Dim>
29 struct ViewAccess;
30
31 template <typename View, unsigned Dim>
32 struct ViewAccess<2, View, Dim> {
33 using index_array_type = typename ippl::RangePolicy<Dim>::index_array_type;
34 KOKKOS_INLINE_FUNCTION constexpr static auto& get(View&& view, unsigned dim1,
35 unsigned dim2,
36 const index_array_type& args) {
37 return apply(view, args)[dim1][dim2];
38 }
39 };
40
41 template <typename View, unsigned Dim>
42 struct ViewAccess<1, View, Dim> {
43 using index_array_type = typename ippl::RangePolicy<Dim>::index_array_type;
44 KOKKOS_INLINE_FUNCTION constexpr static auto& get(View&& view, unsigned dim1,
45 [[maybe_unused]] unsigned dim2,
46 const index_array_type& args) {
47 return apply(view, args)[dim1];
48 }
49 };
50
51 template <typename View, unsigned Dim>
52 struct ViewAccess<0, View, Dim> {
53 using index_array_type = typename ippl::RangePolicy<Dim>::index_array_type;
54 KOKKOS_INLINE_FUNCTION constexpr static auto& get(View&& view,
55 [[maybe_unused]] unsigned dim1,
56 [[maybe_unused]] unsigned dim2,
57 const index_array_type& args) {
58 return apply(view, args);
59 }
60 };
61
62 // Pack a 3D view region into a linear buffer via the real-part of each
63 // cell (FFT-solver semantics: tolerates real or Kokkos::complex
64 // views; discards imaginary parts of complex views).
65 template <typename Tb, typename View, unsigned Dim>
66 inline void pack(const ippl::NDIndex<Dim> intersect, View& view,
68 const ippl::NDIndex<Dim> ldom,
69 ippl::mpi::Communicator::size_type& nsends) {
70 using index_array_type = typename ippl::RangePolicy<Dim>::index_array_type;
71
72 Kokkos::View<Tb*>& buffer = fd.buffer;
73
74 size_t size = intersect.size();
75 nsends = size;
76 if (buffer.size() < size) {
77 const int overalloc = ippl::Comm->getDefaultOverallocation();
78 Kokkos::realloc(buffer, size * overalloc);
79 }
80
81 using index_type = typename ippl::RangePolicy<Dim>::index_type;
82 Kokkos::Array<index_type, Dim> first, last;
83 for (unsigned d = 0; d < Dim; ++d) {
84 first[d] = intersect[d].first() - ldom[d].first() + nghost;
85 last[d] = intersect[d].last() - ldom[d].first() + nghost + 1;
86 }
87
88 ippl::parallel_for("ippl::detail::pack()", ippl::createRangePolicy(first, last),
89 KOKKOS_LAMBDA(const index_array_type& args) {
90 Vector<int, Dim> igVec = args;
91 for (unsigned d = 0; d < Dim; ++d) {
92 igVec[d] -= first[d];
93 }
94
95 int l = igVec[0];
96 for (unsigned d = 1; d < Dim; ++d) {
97 int factor = 1;
98 for (unsigned d1 = 0; d1 < d; ++d1) {
99 factor *= intersect[d1].length();
100 }
101 l += igVec[d] * factor;
102 }
103
104 Kokkos::complex<Tb> val = apply(view, args);
105 buffer(l) = Kokkos::real(val);
106 });
107 Kokkos::fence();
108 }
109
110 // Pack a 3D view region into a linear buffer by direct element copy.
111 // Works for any value type (scalar real, ippl::Vector, ...). Used by
112 // mirrorField and any caller that needs value-preserving communication.
113 template <typename Tb, typename View, unsigned Dim>
114 inline void pack_field(const ippl::NDIndex<Dim> intersect, View& view,
115 ippl::detail::FieldBufferData<Tb>& fd, int nghost,
116 const ippl::NDIndex<Dim> ldom,
117 ippl::mpi::Communicator::size_type& nsends) {
118 using index_array_type = typename ippl::RangePolicy<Dim>::index_array_type;
119
120 Kokkos::View<Tb*>& buffer = fd.buffer;
121
122 size_t size = intersect.size();
123 nsends = size;
124 if (buffer.size() < size) {
125 const int overalloc = ippl::Comm->getDefaultOverallocation();
126 Kokkos::realloc(buffer, size * overalloc);
127 }
128
129 using index_type = typename ippl::RangePolicy<Dim>::index_type;
130 Kokkos::Array<index_type, Dim> first, last;
131 for (unsigned d = 0; d < Dim; ++d) {
132 first[d] = intersect[d].first() - ldom[d].first() + nghost;
133 last[d] = intersect[d].last() - ldom[d].first() + nghost + 1;
134 }
135
136 ippl::parallel_for("ippl::detail::pack_field()", ippl::createRangePolicy(first, last),
137 KOKKOS_LAMBDA(const index_array_type& args) {
138 Vector<int, Dim> igVec = args;
139 for (unsigned d = 0; d < Dim; ++d) {
140 igVec[d] -= first[d];
141 }
142
143 int l = igVec[0];
144 for (unsigned d = 1; d < Dim; ++d) {
145 int factor = 1;
146 for (unsigned d1 = 0; d1 < d; ++d1) {
147 factor *= intersect[d1].length();
148 }
149 l += igVec[d] * factor;
150 }
151 buffer(l) = apply(view, args);
152 });
153 Kokkos::fence();
154 }
155
156 // Unpack a linear buffer into a view region, with per-axis conditional
157 // reflection of the buffer index. Setting a dimension to true via the
158 // coordBool list reverses the buffer ordering along that axis as it is
159 // placed into the view — the primitive operation behind `mirrorField`
160 // and behind the Vico solver's reflected-quadrant assembly.
161 template <int tensorRank, typename Tb, typename View, unsigned Dim>
162 inline void unpack_impl(const ippl::NDIndex<Dim> intersect,
163 const View& view,
164 ippl::detail::FieldBufferData<Tb>& fd, int nghost,
165 const ippl::NDIndex<Dim> ldom,
166 ippl::Vector<bool, Dim> coordBool,
167 size_t dim1 = 0, size_t dim2 = 0) {
168 using index_array_type = typename ippl::RangePolicy<Dim>::index_array_type;
169
170 Kokkos::View<Tb*>& buffer = fd.buffer;
171
172 using index_type = typename ippl::RangePolicy<Dim>::index_type;
173 Kokkos::Array<index_type, Dim> first, last;
174 for (unsigned d = 0; d < Dim; ++d) {
175 first[d] = intersect[d].first() - ldom[d].first() + nghost;
176 last[d] = intersect[d].last() - ldom[d].first() + nghost + 1;
177 }
178
179 ippl::parallel_for("ippl::detail::unpack_impl()",
180 ippl::createRangePolicy(first, last),
181 KOKKOS_LAMBDA(const index_array_type& args) {
182 Vector<int, Dim> igVec = args;
183 for (unsigned d = 0; d < Dim; ++d) {
184 igVec[d] -= first[d];
185 }
186
187
188 for (unsigned d = 0; d < Dim; ++d) {
189 igVec[d] = coordBool[d]
190 * (intersect[d].length() - 2 * igVec[d] - 1)
191 + igVec[d];
192 }
193
194 int l = igVec[0];
195 for (unsigned d = 1; d < Dim; ++d) {
196 int factor = 1;
197 for (unsigned d1 = 0; d1 < d; ++d1) {
198 factor *= intersect[d1].length();
199 }
200 l += igVec[d] * factor;
201 }
202
203 ippl::detail::ViewAccess<tensorRank, decltype(view), Dim>::get(view,
204 dim1, dim2, args) = buffer(l);
205 });
206 Kokkos::fence();
207 }
208
209 template <typename Tb, typename View, unsigned Dim>
210 inline void unpack(const ippl::NDIndex<Dim> intersect, const View& view,
211 ippl::detail::FieldBufferData<Tb>& fd, int nghost,
212 const ippl::NDIndex<Dim> ldom,
213 ippl::Vector<bool, Dim> coordBool = false) {
214 unpack_impl<0, Tb, View, Dim>(intersect, view, fd, nghost, ldom, coordBool);
215 }
216
217 template <typename Tb, typename View, unsigned Dim>
218 inline void unpack(const ippl::NDIndex<Dim> intersect,
219 const View& view, size_t dim1,
221 int nghost, const ippl::NDIndex<Dim> ldom,
222 ippl::Vector<bool, Dim> coordBool = false) {
223 unpack_impl<1, Tb, View, Dim>(intersect, view, fd, nghost, ldom, coordBool,
224 dim1);
225 }
226
227 template <typename Tb, typename View, unsigned Dim>
228 inline void unpack(const ippl::NDIndex<Dim> intersect,
229 View& view, ippl::detail::FieldBufferData<Tb>& fd,
230 int nghost, const ippl::NDIndex<Dim> ldom,
231 size_t dim1, size_t dim2,
232 ippl::Vector<bool, Dim> coordBool = false) {
233 unpack_impl<2, Tb, View, Dim>(intersect, view, fd, nghost, ldom, coordBool,
234 dim1, dim2);
235 }
236
237 // Async MPI_Isend wrapper: pack the intersection into a device-resident
238 // buffer (picked up from the view's memory space via Comm->getBuffer),
239 // issue Comm->isend, and append the resulting request to `requests`.
240 template <typename Tb, typename View, unsigned Dim>
241 inline void solver_send(int TAG, int id, int i, const ippl::NDIndex<Dim> intersection,
242 const ippl::NDIndex<Dim> ldom, int nghost,
243 View& view, ippl::detail::FieldBufferData<Tb>& fd,
244 std::vector<MPI_Request>& requests) {
245 using memory_space = typename View::memory_space;
246
247 requests.resize(requests.size() + 1);
248
249 ippl::mpi::Communicator::size_type nsends;
250 pack(intersection, view, fd, nghost, ldom, nsends);
251
252 ippl::mpi::Communicator::buffer_type<memory_space> buf =
253 ippl::Comm->getBuffer<memory_space, Tb>(nsends);
254
255 int tag = TAG + id;
256
257 ippl::Comm->isend(i, tag, fd, *buf, requests.back(), nsends);
258 buf->resetWritePos();
259 }
260
261 template <typename Tb, typename View, unsigned Dim>
262 inline void solver_recv(int TAG, int id, int i, const ippl::NDIndex<Dim> intersection,
263 const ippl::NDIndex<Dim> ldom, int nghost,
264 View& view, ippl::detail::FieldBufferData<Tb>& fd,
265 ippl::Vector<bool, Dim> coordBool = false) {
266 using memory_space = typename View::memory_space;
267
268 ippl::mpi::Communicator::size_type nrecvs;
269 nrecvs = intersection.size();
270
271 ippl::mpi::Communicator::buffer_type<memory_space> buf =
272 ippl::Comm->getBuffer<memory_space, Tb>(nrecvs);
273
274 int tag = TAG + id;
275
276 ippl::Comm->recv(i, tag, fd, *buf, nrecvs * sizeof(Tb), nrecvs);
277 buf->resetReadPos();
278
279 unpack(intersection, view, fd, nghost, ldom, coordBool);
280 }
281
282 // Variant of solver_send using pack_field (direct element copy). Needed
283 // when the view's element type is not a real scalar (e.g. ippl::Vector).
284 template <typename Tb, typename View, unsigned Dim>
285 inline void solver_send_field(int TAG, int id, int i,
286 const ippl::NDIndex<Dim> intersection,
287 const ippl::NDIndex<Dim> ldom, int nghost,
288 View& view,
290 std::vector<MPI_Request>& requests) {
291 using memory_space = typename View::memory_space;
292
293 requests.resize(requests.size() + 1);
294
295 ippl::mpi::Communicator::size_type nsends;
296 pack_field(intersection, view, fd, nghost, ldom, nsends);
297
298 ippl::mpi::Communicator::buffer_type<memory_space> buf =
299 ippl::Comm->getBuffer<memory_space, Tb>(nsends);
300
301 int tag = TAG + id;
302
303 ippl::Comm->isend(i, tag, fd, *buf, requests.back(), nsends);
304 buf->resetWritePos();
305 }
306
307 } // namespace detail
308} // namespace ippl
309
310#endif // IPPL_FIELD_BUFFER_OPS_HPP
Definition NDIndex.h:21
KOKKOS_INLINE_FUNCTION unsigned size() const noexcept
Definition NDIndex.hpp:32
Definition Vector.h:23
Definition Archive.h:20
KOKKOS_INLINE_FUNCTION constexpr decltype(auto) apply(const View &view, const Coords &coords)
Definition IpplOperations.h:64
RangePolicy< Dim, PolicyArgs... >::policy_type createRangePolicy(const Kokkos::Array< typename RangePolicy< Dim, PolicyArgs... >::index_type, Dim > &begin, const Kokkos::Array< typename RangePolicy< Dim, PolicyArgs... >::index_type, Dim > &end)
Definition ParallelDispatch.h:88
KOKKOS_INLINE_FUNCTION auto & get(Tuple< Ts... > &t)
Accessor function to get an element mutable reference at a specific index from a Tuple.
Definition Tuple.h:314
Definition HaloCells.h:23
Definition FieldBufferOps.hpp:29