OPALX (Object Oriented Parallel Accelerator Library for Exascal) master (dc2a29eed580)
OPALX
Loading...
Searching...
No Matches
FieldMirror.hpp
Go to the documentation of this file.
1//
2// Helper opalx::detail::mirrorField
3//
4// Produce a spatially-mirrored copy of an ippl::Field along a single axis: the
5// cell at global index (i0, i1, i2) in `dst` receives the value from
6// (i0, i1, i2) with the coordinate on `axis` replaced by (N_axis - 1 - idx).
7//
8// GPU + MPI aware: packs and unpacks on device via Kokkos::parallel_for, and
9// uses ippl::Comm->isend / ippl::Comm->recv which hand device pointers to the
10// MPI implementation when CUDA-aware MPI is available. No host staging in the
11// common path.
12//
13// Intended use: after a shifted-Green's-function Poisson solve, mirror the
14// resulting potential / E-field across the cathode plane to obtain the image
15// contribution. Sign handling for the vector components is left to the caller
16// (e.g. BinnedFieldSolver::accumulateFieldToTemp flips the perpendicular E
17// components after the spatial reflection).
18//
19#ifndef OPALX_FIELD_MIRROR_HPP
20#define OPALX_FIELD_MIRROR_HPP
21
22#include <cassert>
23#include <vector>
24
25#include "Ippl.h"
26
27#include "Communicate/Communicator.h"
28#include "Communicate/Tags.h"
29#include "Field/FieldBufferOps.hpp"
30#include "FieldLayout/FieldLayout.h"
31#include "Index/NDIndex.h"
32
33namespace opalx {
34 namespace detail {
35
36 // Spatially mirror `src` into `dst` along `axis`.
37 //
38 // Requirements:
39 // - src and dst have identical layout, mesh, and ghost count.
40 // - axis < Field::dim.
41 // - Dim == 3 (only 3D supported for now).
42 //
43 // In-place (same Field for src and dst) is supported via a scratch copy.
44 template <typename Field>
45 void mirrorField(const Field& src, Field& dst, unsigned axis) {
46 constexpr unsigned Dim = Field::dim;
47 static_assert(Dim == 3, "mirrorField: only Dim == 3 is implemented");
48 assert(axis < Dim);
49 assert(&src.getLayout() == &dst.getLayout()
50 && "mirrorField: src and dst must share a layout");
51 assert(src.getNghost() == dst.getNghost()
52 && "mirrorField: src and dst must have equal ghost counts");
53
54 using view_type = typename Field::view_type;
55 using value_type = typename view_type::value_type;
56
57 // In-place safety: read-before-write via a scratch copy of src.
58 if (static_cast<const void*>(&src) == static_cast<const void*>(&dst)) {
59 Field scratch = src.deepCopy();
60 mirrorField(scratch, dst, axis);
61 return;
62 }
63
64 const auto& layout = src.getLayout();
65 const auto& ldom_r = layout.getLocalNDIndex();
66 const auto& gdom = layout.getDomain();
67 const auto& hDomains = layout.getHostLocalDomains();
68 const int nghost = src.getNghost();
69 const int N_glob = static_cast<int>(gdom[axis].length());
70 const int P = ippl::Comm->size();
71 const int r = ippl::Comm->rank();
72
73 auto srcView = src.getView();
74 auto dstView = dst.getView();
75
76 // Deterministic zero-init: only cells inside a mirror-intersect region
77 // are overwritten below, so ghost cells and any residual interior stay 0.
78 Kokkos::deep_copy(dstView, value_type{});
79
80 // Reflect p's local domain on the mirror axis (other axes untouched).
81 auto reflectOnAxis = [&](const ippl::NDIndex<Dim>& ldom_p) {
82 ippl::NDIndex<Dim> out;
83 for (unsigned d = 0; d < Dim; ++d) {
84 if (d == axis) {
85 const int k0_p = ldom_p[d].first();
86 const int k1_p = k0_p + static_cast<int>(ldom_p[d].length());
87 out[d] = ippl::Index(N_glob - k1_p, N_glob - k0_p - 1);
88 } else {
89 out[d] = ldom_p[d];
90 }
91 }
92 return out;
93 };
94
95 std::vector<MPI_Request> requests;
96 ippl::detail::FieldBufferData<value_type> fd_send;
97 ippl::detail::FieldBufferData<value_type> fd_recv;
98 const int base_tag = ippl::mpi::tag::FIELD_MIRROR;
99
100 const bool flipX = (axis == 0);
101 const bool flipY = (axis == 1);
102 const bool flipZ = (axis == 2);
103
104 // Phase 1: post all non-blocking sends (avoids deadlock with phase-2 recvs).
105 for (int p = 0; p < P; ++p) {
106 if (p == r) {
107 continue;
108 }
109 ippl::NDIndex<Dim> ldom_p_mirror = reflectOnAxis(hDomains(p));
110 if (!ldom_r.touches(ldom_p_mirror)) {
111 continue;
112 }
113 ippl::NDIndex<Dim> intersect = ldom_r.intersect(ldom_p_mirror);
114
115 // Pack r's src data at `intersect` (r's src global coords) and send to p.
116 // p will unpack the buffer with the mirror-axis flip flag, placing
117 // the data at the reflected dst range.
118 ippl::detail::solver_send_field(
119 base_tag, 0, p, intersect, ldom_r, nghost, srcView, fd_send, requests);
120 }
121
122 // Phase 2: blocking recvs + unpack-with-flip on the mirror axis.
123 for (int p = 0; p < P; ++p) {
124 if (p == r) {
125 continue;
126 }
127 ippl::NDIndex<Dim> ldom_p_mirror = reflectOnAxis(hDomains(p));
128 if (!ldom_r.touches(ldom_p_mirror)) {
129 continue;
130 }
131 ippl::NDIndex<Dim> intersect = ldom_r.intersect(ldom_p_mirror);
132
133 // recv
134 ippl::Vector<bool, Dim> coordBool = false;
135 if constexpr (Dim > 0) coordBool[0] = flipX;
136 if constexpr (Dim > 1) coordBool[1] = flipY;
137 if constexpr (Dim > 2) coordBool[2] = flipZ;
138
139 ippl::detail::solver_recv(
140 base_tag, 0, p, intersect, ldom_r, nghost, dstView, fd_recv, coordBool);
141 }
142
143 // Self overlap: r holds both src and dst cells that participate in its
144 // own mirror. Common (and only populated) case on single-rank runs.
145 {
146 ippl::NDIndex<Dim> ldom_r_mirror = reflectOnAxis(ldom_r);
147 if (ldom_r.touches(ldom_r_mirror)) {
148 ippl::NDIndex<Dim> intersect = ldom_r.intersect(ldom_r_mirror);
149
150 const int first0 = intersect[0].first() + nghost - ldom_r[0].first();
151 const int first1 = intersect[1].first() + nghost - ldom_r[1].first();
152 const int first2 = intersect[2].first() + nghost - ldom_r[2].first();
153 const int last0 = intersect[0].last() + nghost - ldom_r[0].first() + 1;
154 const int last1 = intersect[1].last() + nghost - ldom_r[1].first() + 1;
155 const int last2 = intersect[2].last() + nghost - ldom_r[2].first() + 1;
156
157 // Map dst local index to src local index on the mirror axis:
158 // dst_global[a] = dst_local[a] - nghost + ldom_r[a].first()
159 // src_global[a] = N_glob - 1 - dst_global[a]
160 // src_local[a] = src_global[a] - ldom_r[a].first() + nghost
161 // = N_glob - 1 - dst_local[a] + 2 * (nghost -
162 // ldom_r[a].first())
163 const int k_shift = (N_glob - 1) + 2 * (nghost - ldom_r[axis].first());
164 const int axis_c = static_cast<int>(axis);
165
166 using mdrange_type = Kokkos::MDRangePolicy<Kokkos::Rank<3>>;
167 Kokkos::parallel_for(
168 "opalx::mirrorField::self",
169 mdrange_type({first0, first1, first2}, {last0, last1, last2}),
170 KOKKOS_LAMBDA(const size_t i, const size_t j, const size_t k) {
171 size_t idx[3] = {i, j, k};
172 idx[axis_c] = static_cast<size_t>(k_shift) - idx[axis_c];
173 dstView(i, j, k) = srcView(idx[0], idx[1], idx[2]);
174 });
175 Kokkos::fence();
176 }
177 }
178
179 if (!requests.empty()) {
180 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
181 }
182 ippl::Comm->freeAllBuffers();
183 }
184
185 } // namespace detail
186} // namespace opalx
187
188#endif // OPALX_FIELD_MIRROR_HPP
typename ippl::detail::ViewType< ippl::Vector< double, Dim >, 1 >::view_type view_type
ippl::Field< T, Dim, Mesh_t< Dim >, Centering_t< Dim >, ViewArgs... > Field
constexpr unsigned Dim
Definition OPALTypes.h:7
TestT value_type
void mirrorField(const Field &src, Field &dst, unsigned axis)