17#ifndef PECLET_CORE_HALO_GRID_HALO_TOPOLOGY_HPP
18#define PECLET_CORE_HALO_GRID_HALO_TOPOLOGY_HPP
37 void pack(
Index localIdx,
char* dst)
const { std::memcpy(dst, &
data[localIdx],
sizeof(T)); }
38 void unpack(
Index localIdx,
const char* src) { std::memcpy(&
data[localIdx], src,
sizeof(T)); }
42 std::memcpy(&v, src,
sizeof(T));
58 indexer_.init(dec.
origins()[rank], dec.
sizes()[rank], ghostWidth);
62 std::map<int, std::vector<Index>> recvIdxByRank;
63 std::map<int, std::vector<Index>> recvGlobByRank;
66 indexer_.forEachAll([&](
const IVec<Dim>& lmd) {
67 if (indexer_.isInner(lmd))
71 for (
int i = 0; i < Dim; ++i) {
72 g[i] = lmd[i] + indexer_.originInclGhost()[i];
74 if (c < 0 || c >= gsize[i]) {
76 c =
wrap(c, gsize[i]);
87 Index ghostLocal = indexer_.localMdToLocal(lmd);
90 selfDst_.push_back(ghostLocal);
91 selfSrc_.push_back(indexer_.globalToLocal(gw));
93 recvIdxByRank[owner].push_back(ghostLocal);
98 for (
auto& [r, idx] : recvIdxByRank) {
99 recvRanks_.push_back(r);
100 recvIdx_.push_back(std::move(idx));
101 recvGlob_.push_back(std::move(recvGlobByRank[r]));
107 auto packNext = [&](std::vector<char>& out) ->
int {
108 if (k >= recvRanks_.size())
110 int dest = recvRanks_[k];
111 const auto& g = recvGlob_[k];
113 out.resize(g.size() *
sizeof(
Index));
114 std::memcpy(out.data(), g.data(), out.size());
117 auto onRecv = [&](
int src, std::vector<char>& msg) {
118 std::size_t n = msg.size() /
sizeof(
Index);
119 const Index* g =
reinterpret_cast<const Index*
>(msg.data());
120 std::vector<Index> sidx(n);
121 for (std::size_t i = 0; i < n; ++i) {
122 sidx[i] = indexer_.globalToLocal(dec.
multiGlobal(g[i]));
124 sendRanks_.push_back(src);
125 sendIdx_.push_back(std::move(sidx));
127 nbx.
exchange(packNext, onRecv, 7301);
130 persistentReady_ =
false;
137 template <
typename Field>
138 void start(Field& field,
int tag = 0) {
139 applySelfCopy(field);
140 const std::size_t es = field.bytesPerElem();
144 recvBufs_.resize(recvRanks_.size());
145 for (std::size_t k = 0; k < recvRanks_.size(); ++k) {
146 recvBufs_[k].resize(recvIdx_[k].size() * es);
147 MPI_Irecv(recvBufs_[k].data(),
static_cast<int>(recvBufs_[k].size()),
MPI_BYTE, recvRanks_[k],
148 tag, comm_, &recvReqs_[k]);
152 sendBufs_.resize(sendRanks_.size());
153 for (std::size_t k = 0; k < sendRanks_.size(); ++k) {
154 auto& buf = sendBufs_[k];
155 buf.resize(sendIdx_[k].size() * es);
156 for (std::size_t i = 0; i < sendIdx_[k].size(); ++i) {
157 field.pack(sendIdx_[k][i], buf.data() + i * es);
159 MPI_Isend(buf.data(),
static_cast<int>(buf.size()),
MPI_BYTE, sendRanks_[k], tag, comm_,
166 template <
typename Field>
168 const std::size_t es = field.bytesPerElem();
170 for (std::size_t done = 0; done < recvReqs_.size(); ++done) {
175 for (std::size_t i = 0; i < recvIdx_[k].size(); ++i) {
176 field.unpack(recvIdx_[k][i], recvBufs_[k].data() + i * es);
179 if (!sendReqs_.empty()) {
185 template <
typename Field>
196 template <
typename Field>
198 applySelfCopy(field);
199 const std::size_t es = field.bytesPerElem();
203 for (std::size_t k = 0; k < sendRanks_.size(); ++k) {
204 char* dst = graphSendBuf_.data() + graphSendDispl_[k];
205 for (std::size_t i = 0; i < sendIdx_[k].size(); ++i) {
206 field.pack(sendIdx_[k][i], dst + i * es);
210 MPI_BYTE, graphRecvBuf_.data(), graphRecvCnt_.data(),
211 graphRecvDispl_.data(),
MPI_BYTE, graphComm_);
213 for (std::size_t k = 0; k < recvRanks_.size(); ++k) {
214 const char* src = graphRecvBuf_.data() + graphRecvDispl_[k];
215 for (std::size_t i = 0; i < recvIdx_[k].size(); ++i) {
216 field.unpack(recvIdx_[k][i], src + i * es);
227 template <
typename Field>
229 const std::size_t es = field.bytesPerElem();
231 std::vector<char> tmp(es);
232 for (std::size_t i = 0; i < selfSrc_.size(); ++i) {
233 field.pack(selfDst_[i], tmp.data());
234 field.addFrom(selfSrc_[i], tmp.data());
237 std::vector<std::vector<char>> rbuf(sendRanks_.size());
239 for (std::size_t k = 0; k < sendRanks_.size(); ++k) {
240 rbuf[k].resize(sendIdx_[k].size() * es);
241 MPI_Irecv(rbuf[k].data(),
static_cast<int>(rbuf[k].size()),
MPI_BYTE, sendRanks_[k], tag,
245 std::vector<std::vector<char>> sbuf(recvRanks_.size());
247 for (std::size_t k = 0; k < recvRanks_.size(); ++k) {
248 sbuf[k].resize(recvIdx_[k].size() * es);
249 for (std::size_t i = 0; i < recvIdx_[k].size(); ++i)
250 field.pack(recvIdx_[k][i], sbuf[k].data() + i * es);
251 MPI_Isend(sbuf[k].data(),
static_cast<int>(sbuf[k].size()),
MPI_BYTE, recvRanks_[k], tag,
255 for (std::size_t done = 0; done < sendRanks_.size(); ++done) {
260 for (std::size_t i = 0; i < sendIdx_[k].size(); ++i)
261 field.addFrom(sendIdx_[k][i], rbuf[k].data() + i * es);
279 t.
sendRanks = std::vector<int>(sendRanks_.begin(), sendRanks_.end());
280 t.
recvRanks = std::vector<int>(recvRanks_.begin(), recvRanks_.end());
281 for (
const auto& v : sendIdx_) {
282 t.
sendCounts.push_back(
static_cast<int>(v.size()));
285 for (
const auto& v : recvIdx_) {
286 t.
recvCounts.push_back(
static_cast<int>(v.size()));
299 for (
auto& v : recvIdx_)
312 void freeGraphComm() {
331 persistentReady_ =
false;
334 void buildRecvLookup() {
335 recvRankPos_.clear();
336 for (std::size_t k = 0; k < recvRanks_.size(); ++k)
337 recvRankPos_[recvRanks_[k]] = k;
340 template <
typename Field>
341 void applySelfCopy(Field& field) {
344 if (selfSrc_.empty())
346 std::vector<char> tmp(field.bytesPerElem());
347 for (std::size_t i = 0; i < selfSrc_.size(); ++i) {
348 field.pack(selfSrc_[i], tmp.data());
349 field.unpack(selfDst_[i], tmp.data());
353 void ensureGraphComm(std::size_t es) {
354 if (persistentReady_ && graphElemSize_ == es)
360 std::vector<int> sources(recvRanks_.begin(), recvRanks_.end());
361 std::vector<int> dests(sendRanks_.begin(), sendRanks_.end());
368 graphSendCnt_.resize(dests.size());
369 graphSendDispl_.resize(dests.size());
371 for (std::size_t k = 0; k < dests.size(); ++k) {
372 graphSendCnt_[k] =
static_cast<int>(sendIdx_[k].size() * es);
373 graphSendDispl_[k] = off;
374 off += graphSendCnt_[k];
376 graphSendBuf_.resize(off);
378 graphRecvCnt_.resize(sources.size());
379 graphRecvDispl_.resize(sources.size());
381 for (std::size_t k = 0; k < sources.size(); ++k) {
382 graphRecvCnt_[k] =
static_cast<int>(recvIdx_[k].size() * es);
383 graphRecvDispl_[k] = off;
384 off += graphRecvCnt_[k];
386 graphRecvBuf_.resize(off);
389 persistentReady_ =
true;
394 decomp::BlockIndexer<Dim> indexer_;
397 std::vector<int> sendRanks_;
398 std::vector<std::vector<Index>> sendIdx_;
399 std::vector<int> recvRanks_;
400 std::vector<std::vector<Index>> recvIdx_;
401 std::vector<std::vector<Index>> recvGlob_;
402 std::map<int, std::size_t> recvRankPos_;
403 std::vector<Index> selfSrc_, selfDst_;
406 std::vector<MPI_Request> sendReqs_, recvReqs_;
407 std::vector<std::vector<char>> sendBufs_, recvBufs_;
412 bool persistentReady_ =
false;
413 std::size_t graphElemSize_ = 0;
414 std::vector<int> graphSendCnt_, graphSendDispl_, graphRecvCnt_, graphRecvDispl_;
415 std::vector<char> graphSendBuf_, graphRecvBuf_;
const std::vector< IVec< Dim > > & sizes() const
const std::vector< IVec< Dim > > & origins() const
const IVec< Dim > & globalSize() const
Index linearGlobal(const IVec< Dim > &g) const
Global multi-index -> global linear index (x-fastest: I = x + y*nx + z*nx*ny).
int ownerOf(const IVec< Dim > &g) const
Owning block index of a global cell coordinate. Caller must wrap into [0, globalSize) first.
IVec< Dim > multiGlobal(Index lin) const
Global linear index -> global multi-index (inverse of linearGlobal).
void start(Field &field, int tag=0)
Post all sends/recvs; returns immediately so the caller can compute the block interior.
void reverseAdd(Field &field, int tag=0)
void buildTopology(const decomp::BlockDecomposer< Dim > &dec, int rank, int ghostWidth, const std::array< bool, Dim > &periodic, MPI_Comm comm=MPI_COMM_WORLD)
Build the halo topology for rank's block of dec, with the given ghost width and per-axis periodicity.
const decomp::BlockIndexer< Dim > & indexer() const
void exchangeNbx(Field &field, int tag=0)
Convenience: full blocking exchange via the NBX-style engine (start + wait).
void wait(Field &field)
Complete a started exchange: wait for receives and scatter them into the ghost cells.
GridHaloTopology(const GridHaloTopology &)=delete
std::size_t numGhostRecv() const
FlatTopology flatten() const
GridHaloTopology & operator=(const GridHaloTopology &)=delete
std::size_t numNeighbors() const
void exchangePersistent(Field &field)
Full exchange via MPI_Neighbor_alltoallv on a cached distributed-graph communicator.
GridHaloTopology()=default
void exchange(PackNext &&packNext, OnRecv &&onRecv, int tag=0)
int MPI_Waitall(int, MPI_Request *, MPI_Status *)
int MPI_Comm_free(MPI_Comm *)
#define MPI_STATUS_IGNORE
#define MPI_STATUSES_IGNORE
int MPI_Waitany(int, MPI_Request *, int *idx, MPI_Status *)
int MPI_Isend(const void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Request *)
int MPI_Finalized(int *f)
int MPI_Irecv(void *, int, MPI_Datatype, int, int, MPI_Comm, MPI_Request *)
int MPI_Neighbor_alltoallv(const void *, const int *, const int *, MPI_Datatype, void *, const int *, const int *, MPI_Datatype, MPI_Comm)
int MPI_Dist_graph_create_adjacent(MPI_Comm c, int, const int *, const int *, int, const int *, const int *, MPI_Info, int, MPI_Comm *out)
Index wrap(Index x, Index n)
Periodic wrap of a coordinate into [0, n): wrap(x, n) = (x % n + n) % n.
std::array< Index, Dim > IVec
Multi-dimensional integer index / coordinate (x-fastest order by convention).
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
A contiguous local array of T (one per cell of the extended block) viewed as a packable field.
void addFrom(Index localIdx, const char *src)
void pack(Index localIdx, char *dst) const
void unpack(Index localIdx, const char *src)
std::size_t bytesPerElem() const
std::vector< int > recvCounts
std::vector< int > sendRanks
std::vector< int > sendCounts
std::vector< Index > recvIdx
std::vector< int > recvRanks
std::vector< Index > selfSrc
std::vector< Index > selfDst
std::vector< Index > sendIdx