core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
distributed_octree.hpp
Go to the documentation of this file.
1// core — distributed adaptive octree over the ORB block decomposition.
2//
3// The suite keeps the ORB *block* decomposition and gives each block its own
4// local octree (block_octree.hpp). DistributedOctree ties the per-rank blocks
5// together: ORB (peclet::core::decomp::BlockDecomposer) assigns a brick of global root
6// cells to each rank, each rank builds a BlockOctree with the matching world
7// geometry, and this class supplies the two pieces of distributed glue:
8//
9// * balance() — cross-block 2:1 (graded) balance to a *global*
10// fixpoint. Local balance + an owner-based NBX round
11// (a leaf at level Lf tells the owner of the cell just
12// across each block face its level; the owner refines
13// any covering leaf at level >= Lf+2), iterated until
14// no rank refines. Detection is from the fine side, so
15// a single probe per face is complete.
16// * faceNeighborGather() — owner-based ghost exchange: for every local leaf and
17// face it returns the neighbouring leaf's field value,
18// local or remote (a two-round NBX request/reply). For
19// a uniform octree this is exactly the structured-grid
20// face-neighbour halo; the same call works graded.
21//
22// Owner lookups go through BlockDecomposer::ownerOf on global root-cell
23// coordinates — the same owner-based, no-Cartesian-assumption pattern GridHaloTopology
24// uses. Self-addressed messages are handled locally (no MPI send-to-self).
25//
26// Header-only, guarded by PECLET_CORE_HAVE_MORTON; uses the MPI shim (peclet/core/common/mpi.hpp,
27// real MPI or the single-rank stub) and the NBX engine.
28#ifndef PECLET_CORE_AMR_DISTRIBUTED_OCTREE_HPP
29#define PECLET_CORE_AMR_DISTRIBUTED_OCTREE_HPP
30
31#ifdef PECLET_CORE_HAVE_MORTON
32
33#include <algorithm>
34#include <array>
35#include <cstdint>
36#include <cstring>
37#include <map>
38#include <utility>
39#include <vector>
40
41#include "morton/morton.hpp"
48
49namespace peclet::core::amr {
50
51template <int Dim, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
53 public:
55 using M = typename Octree::M;
56 using Code = typename Octree::Code;
57 using Coord = typename Octree::Coord;
58
59 static constexpr double kNoNeighbor = -1e300;
60
61 DistributedOctree() = default;
62
66 std::array<bool, Dim> periodic, MPI_Comm comm = MPI_COMM_WORLD) {
67 comm_ = comm;
68 MPI_Comm_rank(comm_, &rank_);
69 MPI_Comm_size(comm_, &size_);
70 globalRootSize_ = globalRootSize;
71 lmax_ = lmax;
72 globalGeo_ = globalGeo;
73 periodic_ = periodic;
74 rootSpan_ = Index(1) << lmax_;
75
76 dec_.init(static_cast<std::size_t>(size_), globalRootSize_);
77 auto blk = dec_.block(static_cast<std::size_t>(rank_));
78 blockOriginRoot_ = blk.origin;
79 blockBrick_ = blk.size;
80 for (int d = 0; d < Dim; ++d) {
81 blockFineOrigin_[d] = blockOriginRoot_[d] * rootSpan_;
82 blockFineSize_[d] = blockBrick_[d] * rootSpan_;
83 globalFineSize_[d] = globalRootSize_[d] * rootSpan_;
84 }
85 local_.init(blockBrick_, lmax_, blockOriginRoot_);
86 }
87
88 // ---- accessors ---------------------------------------------------------
89 Octree& local() { return local_; }
90 const Octree& local() const { return local_; }
91 int rank() const { return rank_; }
92 int size() const { return size_; }
93 MPI_Comm comm() const { return comm_; }
94 unsigned lmax() const { return lmax_; }
95 const IVec<Dim>& blockOriginRoot() const { return blockOriginRoot_; }
96 const IVec<Dim>& blockBrick() const { return blockBrick_; }
97 const IVec<Dim>& globalRootSize() const { return globalRootSize_; }
98 const IVec<Dim>& blockFineOrigin() const { return blockFineOrigin_; }
99 const IVec<Dim>& globalFineSize() const { return globalFineSize_; }
100 const std::array<bool, Dim>& periodic() const { return periodic_; }
101 Index rootSpan() const { return rootSpan_; }
102 double h0() const { return globalGeo_.h0; }
103 const AmrGeometry<Dim>& globalGeometry() const { return globalGeo_; }
104
107 auto o = M::from_code(local_.code(i)).decode();
108 IVec<Dim> g{};
109 for (int d = 0; d < Dim; ++d)
110 g[d] = static_cast<Index>(o[d]) / rootSpan_ + blockOriginRoot_[d];
111 return g;
112 }
113
117 std::array<Coord, Dim> lc{};
118 for (int d = 0; d < Dim; ++d) {
119 long v = g[d] - blockOriginRoot_[d];
120 if (v < 0 || v >= blockBrick_[d])
121 return -1;
122 lc[d] = static_cast<Coord>(v * rootSpan_);
123 }
124 return local_.find(M::encode(lc).code());
125 }
126
129 AmrGeometry<Dim> g = globalGeo_;
130 for (int d = 0; d < Dim; ++d)
131 g.origin[d] = globalGeo_.origin[d] + static_cast<Real>(blockFineOrigin_[d]) * globalGeo_.h0;
132 return g;
133 }
134
137 auto b = local_.bounds(i);
138 std::array<Coord, Dim> g{};
139 for (int d = 0; d < Dim; ++d)
140 g[d] = static_cast<Coord>(b[0][d] + blockFineOrigin_[d]);
141 return M::encode(g).code();
142 }
143
144 // ---- cross-block 2:1 balance ------------------------------------------
145
149 Index grandTotal = 0;
150 for (;;) {
151 Index work = local_.balance2to1();
152
153 std::map<int, std::vector<char>> sendBufs;
154 std::vector<Code> toRefine;
155 auto consider = [&](const std::array<Coord, Dim>& gc, int Lf) {
156 Index leaf = locateGlobal(gc);
157 if (leaf >= 0 && static_cast<int>(local_.level(leaf)) >= Lf + 2)
158 toRefine.push_back(local_.code(leaf));
159 };
160
161 for (Index i = 0; i < local_.numLeaves(); ++i) {
162 const int Lf = static_cast<int>(local_.level(i));
163 for (int axis = 0; axis < Dim; ++axis)
164 for (int dir = -1; dir <= 1; dir += 2) {
165 std::array<Coord, Dim> gc{};
166 int owner = -1;
167 Index lnb = -1;
168 if (neighborInfo(i, axis, dir, gc, owner, lnb) != Remote)
169 continue;
170 if (owner == rank_)
171 consider(gc, Lf);
172 else
173 appendQuery(sendBufs[owner], gc, Lf);
174 }
175 }
176
177 halo::NbxEngine eng(comm_);
178 auto it = sendBufs.begin();
179 eng.exchange(
180 [&](std::vector<char>& out) -> int {
181 if (it == sendBufs.end())
182 return -1;
183 out = it->second;
184 int dest = it->first;
185 ++it;
186 return dest;
187 },
188 [&](int, std::vector<char>& msg) {
189 parseQueries(msg, [&](const std::array<Coord, Dim>& gc, int Lf) { consider(gc, Lf); });
190 });
191
192 std::sort(toRefine.begin(), toRefine.end());
193 toRefine.erase(std::unique(toRefine.begin(), toRefine.end()), toRefine.end());
194 Index nref = local_.refineIf([&](Code c, unsigned) {
195 return std::binary_search(toRefine.begin(), toRefine.end(), c);
196 });
197 work += nref;
198 grandTotal += nref;
199
200 long lw = static_cast<long>(work), gw = 0;
201 MPI_Allreduce(&lw, &gw, 1, MPI_LONG, MPI_SUM, comm_);
202 if (gw == 0)
203 break;
204 }
205 return grandTotal;
206 }
207
208 // ---- dynamic load re-balancing ----------------------------------------
209
221 Index rebalance(std::vector<std::vector<double>>& fields) {
222 const int K = static_cast<int>(fields.size());
223 const Index n = local_.numLeaves();
224
225 // 1. Weight grid: number of octree leaves under each *global root cell*,
226 // agreed across ranks (blocks are disjoint, so a SUM-Allreduce of the
227 // zero-padded local counts yields the full global grid).
228 std::size_t ncells = 1;
229 for (int d = 0; d < Dim; ++d)
230 ncells *= static_cast<std::size_t>(globalRootSize_[d]);
231 std::vector<double> localWeight(ncells, 0.0), weight(ncells, 0.0);
232 for (Index i = 0; i < n; ++i)
233 localWeight[static_cast<std::size_t>(dec_.linearGlobal(globalRootOf(i)))] += 1.0;
234 MPI_Allreduce(localWeight.data(), weight.data(), static_cast<int>(ncells), MPI_DOUBLE, MPI_SUM,
235 comm_);
236
237 // 2. Weighted re-decomposition over the same global root grid.
238 decomp::BlockDecomposer<Dim> newDec(static_cast<std::size_t>(size_), globalRootSize_, weight);
239 auto nblk = newDec.block(static_cast<std::size_t>(rank_));
240 const IVec<Dim> newOriginRoot = nblk.origin;
241 const IVec<Dim> newBrick = nblk.size;
243 for (int d = 0; d < Dim; ++d)
244 newFineOrigin[d] = newOriginRoot[d] * rootSpan_;
245
246 // 3. Classify every local leaf by its new owner; keep mine, pack the rest.
247 // Leaves are carried by *global* code so the receiver can rebase them.
248 std::vector<Code> codes;
249 std::vector<std::uint8_t> levels;
250 std::vector<std::vector<double>> cols(static_cast<std::size_t>(K));
251 std::map<int, std::vector<char>> sendBufs;
252 Index migratedOut = 0;
253 for (Index i = 0; i < n; ++i) {
254 const Code gc = globalCode(i);
255 const std::uint8_t lv = static_cast<std::uint8_t>(local_.level(i));
256 const int newOwner = newDec.ownerOf(globalRootOf(i));
257 if (newOwner == rank_) {
258 codes.push_back(gc);
259 levels.push_back(lv);
260 for (int c = 0; c < K; ++c)
261 cols[static_cast<std::size_t>(c)].push_back(fields[c][static_cast<std::size_t>(i)]);
262 } else {
263 appendLeaf(sendBufs[newOwner], gc, lv, fields, i, K);
264 ++migratedOut;
265 }
266 }
267
268 // 4. Deliver migrated leaves to their new owners (sparse NBX).
269 {
270 halo::NbxEngine eng(comm_);
271 auto it = sendBufs.begin();
272 eng.exchange(
273 [&](std::vector<char>& out) -> int {
274 if (it == sendBufs.end())
275 return -1;
276 out = it->second;
277 int dest = it->first;
278 ++it;
279 return dest;
280 },
281 [&](int, std::vector<char>& msg) {
282 parseLeaves(msg, K, [&](Code gc, std::uint8_t lv, const double* comps) {
283 codes.push_back(gc);
284 levels.push_back(lv);
285 for (int c = 0; c < K; ++c)
286 cols[static_cast<std::size_t>(c)].push_back(comps[c]);
287 });
288 });
289 }
290
291 // 5. Rebase global codes to the new block origin and sort into Z-order,
292 // carrying the field columns along with the permutation.
293 const std::size_t m = codes.size();
294 std::vector<Code> localCodes(m);
295 for (std::size_t j = 0; j < m; ++j) {
296 auto g = M::from_code(codes[j]).decode();
297 std::array<Coord, Dim> lc{};
298 for (int d = 0; d < Dim; ++d)
299 lc[d] = static_cast<Coord>(g[d] - newFineOrigin[d]);
300 localCodes[j] = M::encode(lc).code();
301 }
302 std::vector<std::size_t> ord(m);
303 for (std::size_t j = 0; j < m; ++j)
304 ord[j] = j;
305 std::sort(ord.begin(), ord.end(),
306 [&](std::size_t a, std::size_t b) { return localCodes[a] < localCodes[b]; });
307
308 std::vector<Code> sortedCodes(m);
309 std::vector<std::uint8_t> sortedLevels(m);
310 std::vector<std::vector<double>> sortedCols(static_cast<std::size_t>(K),
311 std::vector<double>(m));
312 for (std::size_t j = 0; j < m; ++j) {
314 sortedLevels[j] = levels[ord[j]];
315 for (int c = 0; c < K; ++c)
316 sortedCols[static_cast<std::size_t>(c)][j] = cols[static_cast<std::size_t>(c)][ord[j]];
317 }
318
319 // 6. Install the new decomposition, block geometry and local octree.
320 local_.assign(newBrick, lmax_, newOriginRoot, std::move(sortedCodes), std::move(sortedLevels));
321 dec_ = newDec;
322 blockOriginRoot_ = newOriginRoot;
323 blockBrick_ = newBrick;
324 blockFineOrigin_ = newFineOrigin;
325 for (int d = 0; d < Dim; ++d)
326 blockFineSize_[d] = newBrick[d] * rootSpan_;
327 // globalFineSize_ is decomposition-independent and unchanged.
328 for (int c = 0; c < K; ++c)
329 fields[c].swap(sortedCols[static_cast<std::size_t>(c)]);
330 return migratedOut;
331 }
332
333 // ---- owner-based face-neighbour gather (the halo) ---------------------
334
338 std::vector<double> faceNeighborGather(const std::vector<double>& field,
339 double sentinel = kNoNeighbor) const {
340 const Index n = local_.numLeaves();
341 const int F = 2 * Dim;
342 std::vector<double> out(static_cast<std::size_t>(n) * F, sentinel);
343
344 // Phase 1: fill in-block / domain directly; build remote requests.
345 std::map<int, std::vector<char>> reqBufs;
346 for (Index i = 0; i < n; ++i)
347 for (int axis = 0; axis < Dim; ++axis)
348 for (int dir = -1; dir <= 1; dir += 2) {
349 const int slot = faceSlot(i, axis, dir, F);
350 std::array<Coord, Dim> gc{};
351 int owner = -1;
352 Index lnb = -1;
353 NbState st = neighborInfo(i, axis, dir, gc, owner, lnb);
354 if (st == InBlock) {
355 if (lnb >= 0)
356 out[static_cast<std::size_t>(slot)] = field[static_cast<std::size_t>(lnb)];
357 } else if (st == Remote) {
358 if (owner == rank_) {
359 Index leaf = locateGlobal(gc);
360 if (leaf >= 0)
361 out[static_cast<std::size_t>(slot)] = field[static_cast<std::size_t>(leaf)];
362 } else {
363 appendRequest(reqBufs[owner], gc, static_cast<std::int64_t>(slot));
364 }
365 } // DomainNone -> leave sentinel
366 }
367
368 // Phase 2: owners answer requests; requesters write replies into `out`.
369 std::map<int, std::vector<char>> replyBufs;
370 {
371 halo::NbxEngine eng(comm_);
372 auto it = reqBufs.begin();
373 eng.exchange(
374 [&](std::vector<char>& outb) -> int {
375 if (it == reqBufs.end())
376 return -1;
377 outb = it->second;
378 int dest = it->first;
379 ++it;
380 return dest;
381 },
382 [&](int src, std::vector<char>& msg) {
383 parseRequests(msg, [&](const std::array<Coord, Dim>& gc, std::int64_t reqId) {
384 Index leaf = locateGlobal(gc);
385 double v = (leaf >= 0) ? field[static_cast<std::size_t>(leaf)] : sentinel;
386 appendReply(replyBufs[src], reqId, v);
387 });
388 },
389 /*tag=*/11); // distinct tag: keep request/reply rounds from aliasing in NBX
390 }
391 {
392 halo::NbxEngine eng(comm_);
393 auto it = replyBufs.begin();
394 eng.exchange(
395 [&](std::vector<char>& outb) -> int {
396 if (it == replyBufs.end())
397 return -1;
398 outb = it->second;
399 int dest = it->first;
400 ++it;
401 return dest;
402 },
403 [&](int, std::vector<char>& msg) {
404 parseReplies(msg, [&](std::int64_t reqId, double v) {
405 out[static_cast<std::size_t>(reqId)] = v;
406 });
407 },
408 /*tag=*/12); // reply round: distinct tag from the request round (11)
409 }
410 return out;
411 }
412
421 std::vector<Index> directSlot, directLeaf;
422 std::vector<std::array<Coord, Dim>>
424 std::vector<Index> remoteSlot;
426 };
427
429 const Index n = local_.numLeaves();
430 const int F = 2 * Dim;
432 p.nFaces = n * F;
433 for (Index i = 0; i < n; ++i)
434 for (int axis = 0; axis < Dim; ++axis)
435 for (int dir = -1; dir <= 1; dir += 2) {
436 const int slot = faceSlot(i, axis, dir, F);
437 std::array<Coord, Dim> gc{};
438 int owner = -1;
439 Index lnb = -1;
440 NbState st = neighborInfo(i, axis, dir, gc, owner, lnb);
441 if (st == InBlock) {
442 if (lnb >= 0) {
443 p.directSlot.push_back(slot);
444 p.directLeaf.push_back(lnb);
445 }
446 } else if (st == Remote) {
447 // owner==rank resolves locally inside coverValues just as the un-planned path does, so
448 // both remote subcases route through remoteCoords — identical values, identical slots.
449 p.remoteCoords.push_back(gc);
450 p.remoteSlot.push_back(slot);
451 } // DomainNone -> leave sentinel
452 }
453 return p;
454 }
455
458 std::vector<double> faceNeighborGather(const FaceGatherPlan& plan,
459 const std::vector<double>& field,
460 double sentinel = kNoNeighbor) const {
461 std::vector<double> out(static_cast<std::size_t>(plan.nFaces), sentinel);
462 for (std::size_t k = 0; k < plan.directSlot.size(); ++k)
463 out[static_cast<std::size_t>(plan.directSlot[k])] =
464 field[static_cast<std::size_t>(plan.directLeaf[k])];
465 std::vector<double> vals = coverValues(plan.remoteCoords, field, sentinel);
466 for (std::size_t k = 0; k < plan.remoteSlot.size(); ++k)
467 out[static_cast<std::size_t>(plan.remoteSlot[k])] = vals[k];
468 return out;
469 }
470
471 // ---- device-resident gather halo topology (C2) -------------------------
472
478 // Locally-resolved faces (InBlock + Remote-but-owner==rank): out[localSlot[k]] =
479 // field[localLeaf[k]].
480 std::vector<Index> localSlot, localLeaf;
481 // Owner side of the value exchange: gather these local leaves (−1 ⇒ send sentinel) and send to
482 // the requester ranks (concatenated per sendRanks/sendCounts, in received reqId order).
483 std::vector<int> sendRanks, sendCounts;
484 std::vector<Index> sendLeaf;
485 // Requester side: scatter the received values into these out-slots (per recvRanks/recvCounts).
486 std::vector<int> recvRanks, recvCounts;
487 std::vector<Index> recvSlot;
489 };
490
497 t.nFaces = plan.nFaces;
498 t.localSlot = plan.directSlot; // InBlock direct fills
499 t.localLeaf = plan.directLeaf;
500 // Classify remote coords; group cross-rank requests by owner with a per-owner reqId = its
501 // position.
502 std::map<int, std::vector<char>> req; // owner -> serialized (coord, reqId) requests
503 std::map<int, std::vector<Index>> recvSlotByOwner; // owner -> out-slots in reqId order
504 for (std::size_t k = 0; k < plan.remoteCoords.size(); ++k) {
505 const std::array<Coord, Dim>& gc = plan.remoteCoords[k];
506 const Index slot = plan.remoteSlot[k];
507 const int owner = ownerOfFine(gc);
508 if (owner == rank_) {
509 const Index leaf = locateGlobal(gc);
510 if (leaf >= 0) {
511 t.localSlot.push_back(slot);
512 t.localLeaf.push_back(leaf);
513 } // leaf < 0 ⇒ leave sentinel
514 } else {
515 const std::int64_t reqId = static_cast<std::int64_t>(recvSlotByOwner[owner].size());
516 appendRequest(req[owner], gc, reqId);
517 recvSlotByOwner[owner].push_back(slot);
518 }
519 }
520 for (auto& kv : recvSlotByOwner) { // requester side (deterministic owner order from std::map)
521 t.recvRanks.push_back(kv.first);
522 t.recvCounts.push_back(static_cast<int>(kv.second.size()));
523 for (Index s : kv.second)
524 t.recvSlot.push_back(s);
525 }
526 // One NBX round: owners receive coord requests and resolve them to local leaves (in reqId
527 // order).
528 std::map<int, std::vector<Index>> sendLeafBySrc;
529 {
530 halo::NbxEngine eng(comm_);
531 auto it = req.begin();
532 eng.exchange(
533 [&](std::vector<char>& o) -> int {
534 if (it == req.end())
535 return -1;
536 o = it->second;
537 int d = it->first;
538 ++it;
539 return d;
540 },
541 [&](int src, std::vector<char>& msg) {
542 std::vector<Index>& leaves = sendLeafBySrc[src];
543 parseRequests(msg, [&](const std::array<Coord, Dim>& gc, std::int64_t reqId) {
544 const Index leaf = locateGlobal(gc);
545 if (static_cast<std::int64_t>(leaves.size()) <= reqId)
546 leaves.resize(static_cast<std::size_t>(reqId) + 1, -1);
547 leaves[static_cast<std::size_t>(reqId)] = leaf;
548 });
549 },
550 /*tag=*/31);
551 }
552 for (auto& kv : sendLeafBySrc) { // owner side
553 t.sendRanks.push_back(kv.first);
554 t.sendCounts.push_back(static_cast<int>(kv.second.size()));
555 for (Index l : kv.second)
556 t.sendLeaf.push_back(l);
557 }
558 return t;
559 }
560
561 // ---- by-coordinate owner gathers (graded consistent-operator halo) ----
562
565 struct FaceInfo {
566 int state = 2;
567 std::array<Coord, Dim> gc{};
568 int owner = -1;
570 };
571 FaceInfo faceAcross(Index i, int axis, int dir) const {
572 FaceInfo f;
573 f.state = static_cast<int>(neighborInfo(i, axis, dir, f.gc, f.owner, f.localNb));
574 return f;
575 }
576
579 std::vector<int> coverLevels(const std::vector<std::array<Coord, Dim>>& coords) const {
580 std::vector<double> out(coords.size(), -1.0);
581 std::map<int, std::vector<char>> req;
582 for (std::size_t idx = 0; idx < coords.size(); ++idx) {
583 int owner = ownerOfFine(coords[idx]);
584 if (owner == rank_) {
585 Index leaf = locateGlobal(coords[idx]);
586 out[idx] = (leaf >= 0) ? static_cast<double>(local_.level(leaf)) : -1.0;
587 } else {
588 appendRequest(req[owner], coords[idx], static_cast<std::int64_t>(idx));
589 }
590 }
591 requestReply(
592 req,
593 [&](const std::array<Coord, Dim>& gc) -> double {
594 Index leaf = locateGlobal(gc);
595 return (leaf >= 0) ? static_cast<double>(local_.level(leaf)) : -1.0;
596 },
597 out, /*tagA=*/21, /*tagB=*/22);
598 std::vector<int> lv(coords.size());
599 for (std::size_t i = 0; i < out.size(); ++i)
600 lv[i] = static_cast<int>(out[i]);
601 return lv;
602 }
603
606 std::vector<double> coverValues(const std::vector<std::array<Coord, Dim>>& coords,
607 const std::vector<double>& field,
608 double sentinel = kNoNeighbor) const {
609 std::vector<double> out(coords.size(), sentinel);
610 std::map<int, std::vector<char>> req;
611 for (std::size_t idx = 0; idx < coords.size(); ++idx) {
612 int owner = ownerOfFine(coords[idx]);
613 if (owner == rank_) {
614 Index leaf = locateGlobal(coords[idx]);
615 if (leaf >= 0)
616 out[idx] = field[static_cast<std::size_t>(leaf)];
617 } else {
618 appendRequest(req[owner], coords[idx], static_cast<std::int64_t>(idx));
619 }
620 }
621 requestReply(
622 req,
623 [&](const std::array<Coord, Dim>& gc) -> double {
624 Index leaf = locateGlobal(gc);
625 return (leaf >= 0) ? field[static_cast<std::size_t>(leaf)] : sentinel;
626 },
627 out, /*tagA=*/23, /*tagB=*/24);
628 return out;
629 }
630
631 private:
632 enum NbState { InBlock, Remote, DomainNone };
633
634 int ownerOfFine(const std::array<Coord, Dim>& gc) const {
636 for (int d = 0; d < Dim; ++d)
637 rootCell[d] = static_cast<Index>(gc[d]) / rootSpan_;
638 return dec_.ownerOf(rootCell);
639 }
640
644 template <class Responder>
645 void requestReply(std::map<int, std::vector<char>>& req, Responder&& respond,
646 std::vector<double>& out, int tagA, int tagB) const {
647 std::map<int, std::vector<char>> rep;
648 {
649 halo::NbxEngine eng(comm_);
650 auto it = req.begin();
651 eng.exchange(
652 [&](std::vector<char>& o) -> int {
653 if (it == req.end())
654 return -1;
655 o = it->second;
656 int d = it->first;
657 ++it;
658 return d;
659 },
660 [&](int src, std::vector<char>& msg) {
661 parseRequests(msg, [&](const std::array<Coord, Dim>& gc, std::int64_t reqId) {
662 appendReply(rep[src], reqId, respond(gc));
663 });
664 },
665 tagA);
666 }
667 {
668 halo::NbxEngine eng(comm_);
669 auto it = rep.begin();
670 eng.exchange(
671 [&](std::vector<char>& o) -> int {
672 if (it == rep.end())
673 return -1;
674 o = it->second;
675 int d = it->first;
676 ++it;
677 return d;
678 },
679 [&](int, std::vector<char>& msg) {
680 parseReplies(msg, [&](std::int64_t reqId, double v) {
681 out[static_cast<std::size_t>(reqId)] = v;
682 });
683 },
684 tagB);
685 }
686 }
687
688 static int faceSlot(Index i, int axis, int dir, int F) {
689 return static_cast<int>(i) * F + 2 * axis + (dir > 0 ? 0 : 1);
690 }
691
695 NbState neighborInfo(Index i, int axis, int dir, std::array<Coord, Dim>& gc, int& owner,
696 Index& localNb) const {
697 auto b = local_.bounds(i);
698 const auto& lo = b[0];
699 const Coord size = Coord(Coord(1) << local_.level(i));
700 const long pc = (dir > 0) ? static_cast<long>(lo[axis]) + static_cast<long>(size)
701 : static_cast<long>(lo[axis]) - 1;
702
703 if (pc >= 0 && pc < static_cast<long>(blockFineSize_[axis])) {
704 std::array<Coord, Dim> p = lo;
705 p[axis] = static_cast<Coord>(pc);
706 localNb = local_.find(M::encode(p).code());
707 return InBlock;
708 }
709
710 std::array<long, Dim> g{};
711 for (int d = 0; d < Dim; ++d)
712 g[d] = static_cast<long>(blockFineOrigin_[d]) + static_cast<long>(lo[d]);
713 g[axis] = static_cast<long>(blockFineOrigin_[axis]) + pc;
714 for (int d = 0; d < Dim; ++d) {
715 const long gf = static_cast<long>(globalFineSize_[d]);
716 if (g[d] < 0 || g[d] >= gf) {
717 if (!periodic_[d])
718 return DomainNone;
719 g[d] = ((g[d] % gf) + gf) % gf;
720 }
721 }
723 for (int d = 0; d < Dim; ++d) {
724 gc[d] = static_cast<Coord>(g[d]);
725 rootCell[d] = static_cast<Index>(g[d] / static_cast<long>(rootSpan_));
726 }
727 owner = dec_.ownerOf(rootCell);
728 return Remote;
729 }
730
733 Index locateGlobal(const std::array<Coord, Dim>& gc) const {
734 std::array<Coord, Dim> lc{};
735 for (int d = 0; d < Dim; ++d)
736 lc[d] = static_cast<Coord>(gc[d] - blockFineOrigin_[d]);
737 return local_.find(M::encode(lc).code());
738 }
739
740 // ---- message (de)serialization ----------------------------------------
741 static void appendBytes(std::vector<char>& buf, const void* p, std::size_t n) {
742 const char* c = static_cast<const char*>(p);
743 buf.insert(buf.end(), c, c + n);
744 }
745 static void appendQuery(std::vector<char>& buf, const std::array<Coord, Dim>& gc, int Lf) {
746 for (int d = 0; d < Dim; ++d) {
747 std::int64_t v = static_cast<std::int64_t>(gc[d]);
748 appendBytes(buf, &v, sizeof(v));
749 }
750 std::int32_t l = Lf;
751 appendBytes(buf, &l, sizeof(l));
752 }
753 template <class Fn>
754 static void parseQueries(const std::vector<char>& buf, Fn&& fn) {
755 std::size_t off = 0;
756 const std::size_t item = Dim * sizeof(std::int64_t) + sizeof(std::int32_t);
757 while (off + item <= buf.size()) {
758 std::array<Coord, Dim> gc{};
759 for (int d = 0; d < Dim; ++d) {
760 std::int64_t v;
761 std::memcpy(&v, buf.data() + off, sizeof(v));
762 off += sizeof(v);
763 gc[d] = static_cast<Coord>(v);
764 }
765 std::int32_t l;
766 std::memcpy(&l, buf.data() + off, sizeof(l));
767 off += sizeof(l);
768 fn(gc, static_cast<int>(l));
769 }
770 }
771 static void appendRequest(std::vector<char>& buf, const std::array<Coord, Dim>& gc,
772 std::int64_t reqId) {
773 for (int d = 0; d < Dim; ++d) {
774 std::int64_t v = static_cast<std::int64_t>(gc[d]);
775 appendBytes(buf, &v, sizeof(v));
776 }
777 appendBytes(buf, &reqId, sizeof(reqId));
778 }
779 template <class Fn>
780 static void parseRequests(const std::vector<char>& buf, Fn&& fn) {
781 std::size_t off = 0;
782 const std::size_t item = (Dim + 1) * sizeof(std::int64_t);
783 while (off + item <= buf.size()) {
784 std::array<Coord, Dim> gc{};
785 for (int d = 0; d < Dim; ++d) {
786 std::int64_t v;
787 std::memcpy(&v, buf.data() + off, sizeof(v));
788 off += sizeof(v);
789 gc[d] = static_cast<Coord>(v);
790 }
791 std::int64_t reqId;
792 std::memcpy(&reqId, buf.data() + off, sizeof(reqId));
793 off += sizeof(reqId);
794 fn(gc, reqId);
795 }
796 }
799 static void appendLeaf(std::vector<char>& buf, Code gc, std::uint8_t level,
800 const std::vector<std::vector<double>>& fields, Index i, int K) {
801 std::int64_t c = static_cast<std::int64_t>(gc);
802 appendBytes(buf, &c, sizeof(c));
803 std::int32_t l = static_cast<std::int32_t>(level);
804 appendBytes(buf, &l, sizeof(l));
805 for (int k = 0; k < K; ++k) {
806 double v = fields[static_cast<std::size_t>(k)][static_cast<std::size_t>(i)];
807 appendBytes(buf, &v, sizeof(v));
808 }
809 }
810 template <class Fn>
811 static void parseLeaves(const std::vector<char>& buf, int K, Fn&& fn) {
812 std::size_t off = 0;
813 const std::size_t item =
814 sizeof(std::int64_t) + sizeof(std::int32_t) + static_cast<std::size_t>(K) * sizeof(double);
815 std::vector<double> comps(static_cast<std::size_t>(K));
816 while (off + item <= buf.size()) {
817 std::int64_t c;
818 std::memcpy(&c, buf.data() + off, sizeof(c));
819 off += sizeof(c);
820 std::int32_t l;
821 std::memcpy(&l, buf.data() + off, sizeof(l));
822 off += sizeof(l);
823 for (int k = 0; k < K; ++k) {
824 std::memcpy(&comps[static_cast<std::size_t>(k)], buf.data() + off, sizeof(double));
825 off += sizeof(double);
826 }
827 fn(static_cast<Code>(c), static_cast<std::uint8_t>(l), comps.data());
828 }
829 }
830 static void appendReply(std::vector<char>& buf, std::int64_t reqId, double v) {
831 appendBytes(buf, &reqId, sizeof(reqId));
832 appendBytes(buf, &v, sizeof(v));
833 }
834 template <class Fn>
835 static void parseReplies(const std::vector<char>& buf, Fn&& fn) {
836 std::size_t off = 0;
837 const std::size_t item = sizeof(std::int64_t) + sizeof(double);
838 while (off + item <= buf.size()) {
839 std::int64_t reqId;
840 std::memcpy(&reqId, buf.data() + off, sizeof(reqId));
841 off += sizeof(reqId);
842 double v;
843 std::memcpy(&v, buf.data() + off, sizeof(v));
844 off += sizeof(v);
845 fn(reqId, v);
846 }
847 }
848
849 MPI_Comm comm_ = MPI_COMM_WORLD;
850 int rank_ = 0;
851 int size_ = 1;
852 decomp::BlockDecomposer<Dim> dec_;
853 Octree local_;
854 AmrGeometry<Dim> globalGeo_{};
855 IVec<Dim> globalRootSize_{};
856 IVec<Dim> blockOriginRoot_{};
857 IVec<Dim> blockBrick_{};
858 IVec<Dim> blockFineOrigin_{};
859 IVec<Dim> blockFineSize_{};
860 IVec<Dim> globalFineSize_{};
861 std::array<bool, Dim> periodic_{};
862 unsigned lmax_ = 0;
863 Index rootSpan_ = 1;
864};
865
866} // namespace peclet::core::amr
867
868#endif // PECLET_CORE_HAVE_MORTON
869#endif // PECLET_CORE_AMR_DISTRIBUTED_OCTREE_HPP
void init(IVec< Dim > brick, unsigned lmax, IVec< Dim > globalOrigin=IVec< Dim >{})
Index balance2to1()
Enforce 2:1 (graded) balance within this block: no two face-adjacent leaves differ by more than one l...
void assign(IVec< Dim > brick, unsigned lmax, IVec< Dim > globalOrigin, std::vector< Code > codes, std::vector< std::uint8_t > levels)
Replace the entire leaf set directly (used by load-balancing migration, which rebuilds a block from l...
Index refineIf(Pred &&pred)
Split every leaf for which pred(code, level) is true (and level > 0) into its 2^Dim children one leve...
unsigned level(Index i) const
Index find(Code p) const
Leaf containing Morton code p, or -1. Host wrapper over amrLocate.
std::array< std::array< Coord, Dim >, 2 > bounds(Index i) const
Inclusive integer bounds [lo, hi] of leaf i in fine units.
const std::array< bool, Dim > & periodic() const
GatherHaloTopology buildGatherHaloTopology(const FaceGatherPlan &plan) const
Build the value-only gather topology from a FaceGatherPlan: classify each remote coord by owner (owne...
const AmrGeometry< Dim > & globalGeometry() const
const IVec< Dim > & blockOriginRoot() const
static constexpr double kNoNeighbor
gather sentinel for "no neighbour"
IVec< Dim > globalRootOf(Index i) const
Global root-cell coordinate of local leaf i (lmax==0: the leaf is one root cell).
FaceInfo faceAcross(Index i, int axis, int dir) const
AmrGeometry< Dim > localGeometry() const
World geometry of this rank's block (the global geometry shifted to its origin).
Index rebalance(std::vector< std::vector< double > > &fields)
Re-decompose by per-root-cell octree-leaf count and migrate leaves (with their field columns) to the ...
Index findGlobalRoot(const IVec< Dim > &g) const
Local leaf covering global root-cell g, or -1 if not owned by this rank (lmax==0).
const IVec< Dim > & globalFineSize() const
std::vector< double > coverValues(const std::vector< std::array< Coord, Dim > > &coords, const std::vector< double > &field, double sentinel=kNoNeighbor) const
For each global fine coord, the covering leaf's field value on its owner (sentinel if none).
std::vector< double > faceNeighborGather(const FaceGatherPlan &plan, const std::vector< double > &field, double sentinel=kNoNeighbor) const
Face-neighbour gather using a precomputed FaceGatherPlan: only the field values move,...
const IVec< Dim > & globalRootSize() const
std::vector< int > coverLevels(const std::vector< std::array< Coord, Dim > > &coords) const
For each global fine coord (already wrapped into the domain), the level of the covering leaf on its o...
void init(IVec< Dim > globalRootSize, unsigned lmax, AmrGeometry< Dim > globalGeo, std::array< bool, Dim > periodic, MPI_Comm comm=MPI_COMM_WORLD)
Decompose a globalRootSize grid of root cells over the communicator and build this rank's local block...
std::vector< double > faceNeighborGather(const std::vector< double > &field, double sentinel=kNoNeighbor) const
For each local leaf and each of the 2*Dim faces, the neighbouring leaf's field value.
Code globalCode(Index i) const
Global Morton code of local leaf i (origin in global fine coordinates).
Index balance()
Bring the whole distributed octree to a 2:1-balanced state.
const IVec< Dim > & blockFineOrigin() const
const IVec< Dim > & blockBrick() const
int MPI_Comm_size(MPI_Comm, int *s)
Definition mpi_stub.hpp:69
int MPI_Allreduce(const void *sbuf, void *rbuf, int count, MPI_Datatype dt, MPI_Op, MPI_Comm)
Definition mpi_stub.hpp:80
int MPI_Comm
Definition mpi_stub.hpp:16
#define MPI_COMM_WORLD
Definition mpi_stub.hpp:26
#define MPI_SUM
Definition mpi_stub.hpp:42
#define MPI_DOUBLE
Definition mpi_stub.hpp:40
int MPI_Comm_rank(MPI_Comm, int *r)
Definition mpi_stub.hpp:65
#define MPI_LONG
Definition mpi_stub.hpp:39
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
double Real
Default host floating type. Device kernels may use float; conversions happen at the boundary.
Definition types.hpp:18
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
Static topology of the face-neighbour gather: which out-slots are filled from a local leaf (directSlo...
std::vector< std::array< Coord, Dim > > remoteCoords
owner-gathered coords (incl. owner==rank)
std::vector< Index > directLeaf
out[directSlot[k]] = field[directLeaf[k]]
std::vector< Index > remoteSlot
out[remoteSlot[k]] = covered value
Classification of leaf i's face on (axis,dir): {state, global fine probe gc, owner,...
Flattened, value-only topology for the face-neighbour gather, established ONCE: the per-matvec exchan...