27#ifndef PECLET_AMR_DISTRIBUTED_OCTREE_HPP
28#define PECLET_AMR_DISTRIBUTED_OCTREE_HPP
39#include "morton/morton.hpp"
43#include "peclet/core/common/mpi.hpp"
44#include "peclet/core/common/types.hpp"
45#include "peclet/core/decomp/block_decomposer.hpp"
46#include "peclet/core/halo/nbx.hpp"
50template <
int Dim,
unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
73 rootSpan_ =
Index(1) << lmax_;
75 dec_.init(
static_cast<std::size_t
>(size_), globalRootSize_);
76 auto blk = dec_.block(
static_cast<std::size_t
>(rank_));
77 blockOriginRoot_ =
blk.origin;
78 blockBrick_ =
blk.size;
79 for (
int d = 0; d < Dim; ++d) {
80 blockFineOrigin_[d] = blockOriginRoot_[d] * rootSpan_;
81 blockFineSize_[d] = blockBrick_[d] * rootSpan_;
82 globalFineSize_[d] = globalRootSize_[d] * rootSpan_;
84 local_.
init(blockBrick_, lmax_, blockOriginRoot_);
100 if (
dec.numBlocks() !=
static_cast<std::size_t
>(size_))
101 throw std::invalid_argument(
102 "amr::DistributedOctree::initDecomposed: the decomposition needs one block per rank");
103 globalRootSize_ =
dec.globalSize();
107 rootSpan_ =
Index(1) << lmax_;
109 auto blk = dec_.block(
static_cast<std::size_t
>(rank_));
110 blockOriginRoot_ =
blk.origin;
111 blockBrick_ =
blk.size;
112 for (
int d = 0; d < Dim; ++d) {
113 blockFineOrigin_[d] = blockOriginRoot_[d] * rootSpan_;
114 blockFineSize_[d] = blockBrick_[d] * rootSpan_;
115 globalFineSize_[d] = globalRootSize_[d] * rootSpan_;
117 local_.
init(blockBrick_, lmax_, blockOriginRoot_);
123 int rank()
const {
return rank_; }
124 int size()
const {
return size_; }
126 unsigned lmax()
const {
return lmax_; }
132 const std::array<bool, Dim>&
periodic()
const {
return periodic_; }
143 double h0Scalar()
const {
return globalGeo_.h0[0]; }
148 auto o = M::from_code(local_.
code(
i)).decode();
150 for (
int d = 0; d < Dim; ++d)
151 g[d] =
static_cast<Index>(
o[d]) / rootSpan_ + blockOriginRoot_[d];
158 std::array<Coord, Dim>
lc{};
159 for (
int d = 0; d < Dim; ++d) {
160 long v = g[d] - blockOriginRoot_[d];
163 lc[d] =
static_cast<Coord>(
v * rootSpan_);
165 return local_.
find(M::encode(
lc).code());
176 for (
int d = 0; d < Dim; ++d)
177 if ((globalRootSize_[d] % 2) != 0 || (blockOriginRoot_[d] % 2) != 0 ||
178 (blockBrick_[d] % 2) != 0)
204 for (
int d = 0; d < Dim; ++d)
206 dec_ = dec_.coarsened(
ratio);
207 for (
int d = 0; d < Dim; ++d) {
208 globalRootSize_[d] /= 2;
209 blockOriginRoot_[d] /= 2;
220 for (
int d = 0; d < Dim; ++d)
222 globalGeo_.origin[d] +
static_cast<Real>(blockFineOrigin_[d]) * globalGeo_.h0[d];
229 std::array<Coord, Dim> g{};
230 for (
int d = 0; d < Dim; ++d)
231 g[d] =
static_cast<Coord>(b[0][d] + blockFineOrigin_[d]);
232 return M::encode(g).code();
244 std::map<int, std::vector<char>>
sendBufs;
246 auto consider = [&](
const std::array<Coord, Dim>& gc,
int Lf) {
253 const int Lf =
static_cast<int>(local_.
level(
i));
254 for (
int axis = 0; axis < Dim; ++axis)
255 for (
int dir = -1; dir <= 1; dir += 2) {
256 std::array<Coord, Dim> gc{};
259 if (neighborInfo(
i, axis, dir, gc, owner,
lnb) != Remote)
268 halo::NbxEngine
eng(comm_);
271 [&](std::vector<char>&
out) ->
int {
279 [&](
int, std::vector<char>&
msg) {
280 parseQueries(
msg, [&](
const std::array<Coord, Dim>& gc,
int Lf) {
consider(gc,
Lf); });
291 long lw =
static_cast<long>(
work),
gw = 0;
313 const int K =
static_cast<int>(
fields.size());
320 for (
int d = 0; d < Dim; ++d)
321 ncells *=
static_cast<std::size_t
>(globalRootSize_[d]);
335 decomp::chooseAlignedWeighted(
static_cast<std::size_t
>(size_), globalRootSize_,
weight);
336 rebalanceAlign_ =
choice.a;
337 rebalanceImbalance_ =
choice.imbalance;
338 decomp::BlockDecomposer<Dim>
newDec = std::move(
choice.dec);
339 auto nblk =
newDec.block(
static_cast<std::size_t
>(rank_));
343 for (
int d = 0; d < Dim; ++d)
348 std::vector<Code> codes;
349 std::vector<std::uint8_t> levels;
350 std::vector<std::vector<double>>
cols(
static_cast<std::size_t
>(
K));
351 std::map<int, std::vector<char>>
sendBufs;
355 const std::uint8_t
lv =
static_cast<std::uint8_t
>(local_.
level(
i));
359 levels.push_back(
lv);
360 for (
int c = 0;
c <
K; ++
c)
361 cols[
static_cast<std::size_t
>(
c)].push_back(
fields[
c][
static_cast<std::size_t
>(
i)]);
370 halo::NbxEngine
eng(comm_);
373 [&](std::vector<char>&
out) ->
int {
381 [&](
int, std::vector<char>&
msg) {
382 parseLeaves(
msg,
K, [&](
Code gc, std::uint8_t
lv,
const double*
comps) {
384 levels.push_back(
lv);
385 for (
int c = 0;
c <
K; ++
c)
393 const std::size_t m = codes.size();
395 for (std::size_t
j = 0;
j < m; ++
j) {
396 auto g = M::from_code(codes[
j]).decode();
397 std::array<Coord, Dim>
lc{};
398 for (
int d = 0; d < Dim; ++d)
402 std::vector<std::size_t>
ord(m);
403 for (std::size_t
j = 0;
j < m; ++
j)
405 std::sort(
ord.begin(),
ord.end(),
406 [&](std::size_t
a, std::size_t b) { return localCodes[a] < localCodes[b]; });
410 std::vector<std::vector<double>>
sortedCols(
static_cast<std::size_t
>(
K),
411 std::vector<double>(m));
412 for (std::size_t
j = 0;
j < m; ++
j) {
415 for (
int c = 0;
c <
K; ++
c)
425 for (
int d = 0; d < Dim; ++d)
426 blockFineSize_[d] =
newBrick[d] * rootSpan_;
428 for (
int c = 0;
c <
K; ++
c)
448 const int F = 2 * Dim;
449 std::vector<double>
out(
static_cast<std::size_t
>(n) *
F,
sentinel);
452 std::map<int, std::vector<char>>
reqBufs;
454 for (
int axis = 0; axis < Dim; ++axis)
455 for (
int dir = -1; dir <= 1; dir += 2) {
456 const int slot = faceSlot(
i, axis, dir,
F);
457 std::array<Coord, Dim> gc{};
460 NbState
st = neighborInfo(
i, axis, dir, gc, owner,
lnb);
463 out[
static_cast<std::size_t
>(slot)] = field[
static_cast<std::size_t
>(
lnb)];
464 }
else if (
st == Remote) {
465 if (owner == rank_) {
468 out[
static_cast<std::size_t
>(slot)] = field[
static_cast<std::size_t
>(
leaf)];
470 appendRequest(
reqBufs[owner], gc,
static_cast<std::int64_t
>(slot));
476 std::map<int, std::vector<char>>
replyBufs;
478 halo::NbxEngine
eng(comm_);
481 [&](std::vector<char>&
outb) ->
int {
489 [&](
int src, std::vector<char>&
msg) {
490 parseRequests(
msg, [&](
const std::array<Coord, Dim>& gc, std::int64_t
reqId) {
499 halo::NbxEngine
eng(comm_);
502 [&](std::vector<char>&
outb) ->
int {
510 [&](
int, std::vector<char>&
msg) {
511 parseReplies(
msg, [&](std::int64_t
reqId,
double v) {
512 out[
static_cast<std::size_t
>(
reqId)] =
v;
529 std::vector<std::array<Coord, Dim>>
537 const int F = 2 * Dim;
541 for (
int axis = 0; axis < Dim; ++axis)
542 for (
int dir = -1; dir <= 1; dir += 2) {
543 const int slot = faceSlot(
i, axis, dir,
F);
544 std::array<Coord, Dim> gc{};
547 NbState
st = neighborInfo(
i, axis, dir, gc, owner,
lnb);
550 p.directSlot.push_back(slot);
551 p.directLeaf.push_back(
lnb);
553 }
else if (
st == Remote) {
556 p.remoteCoords.push_back(gc);
557 p.remoteSlot.push_back(slot);
566 const std::vector<double>& field,
568 std::vector<double>
out(
static_cast<std::size_t
>(
plan.nFaces),
sentinel);
569 for (std::size_t k = 0; k <
plan.directSlot.size(); ++k)
570 out[
static_cast<std::size_t
>(
plan.directSlot[k])] =
571 field[
static_cast<std::size_t
>(
plan.directLeaf[k])];
573 for (std::size_t k = 0; k <
plan.remoteSlot.size(); ++k)
574 out[
static_cast<std::size_t
>(
plan.remoteSlot[k])] =
vals[k];
605 t.localSlot =
plan.directSlot;
606 t.localLeaf =
plan.directLeaf;
609 std::map<int, std::vector<char>>
req;
611 for (std::size_t k = 0; k <
plan.remoteCoords.size(); ++k) {
612 const std::array<Coord, Dim>& gc =
plan.remoteCoords[k];
614 const int owner = ownerOfFine(gc);
615 if (owner == rank_) {
618 t.localSlot.push_back(slot);
619 t.localLeaf.push_back(
leaf);
623 appendRequest(
req[owner], gc,
reqId);
628 t.recvRanks.push_back(
kv.first);
629 t.recvCounts.push_back(
static_cast<int>(
kv.second.size()));
631 t.recvSlot.push_back(
s);
637 halo::NbxEngine
eng(comm_);
638 auto it =
req.begin();
640 [&](std::vector<char>&
o) ->
int {
648 [&](
int src, std::vector<char>&
msg) {
650 parseRequests(
msg, [&](
const std::array<Coord, Dim>& gc, std::int64_t
reqId) {
652 if (
static_cast<std::int64_t
>(
leaves.size()) <=
reqId)
653 leaves.resize(
static_cast<std::size_t
>(
reqId) + 1, -1);
660 t.sendRanks.push_back(
kv.first);
661 t.sendCounts.push_back(
static_cast<int>(
kv.second.size()));
663 t.sendLeaf.push_back(
l);
674 std::array<Coord, Dim>
gc{};
680 f.
state =
static_cast<int>(neighborInfo(
i, axis, dir,
f.gc,
f.owner,
f.localNb));
687 std::vector<double>
out(
coords.size(), -1.0);
688 std::map<int, std::vector<char>>
req;
689 for (std::size_t idx = 0; idx <
coords.size(); ++idx) {
690 int owner = ownerOfFine(
coords[idx]);
691 if (owner == rank_) {
695 appendRequest(
req[owner],
coords[idx],
static_cast<std::int64_t
>(idx));
700 [&](
const std::array<Coord, Dim>& gc) ->
double {
702 return (
leaf >= 0) ?
static_cast<double>(local_.
level(
leaf)) : -1.0;
706 for (std::size_t
i = 0;
i <
out.size(); ++
i)
707 lv[
i] =
static_cast<int>(
out[
i]);
714 const std::vector<double>& field,
717 std::map<int, std::vector<char>>
req;
718 for (std::size_t idx = 0; idx <
coords.size(); ++idx) {
719 int owner = ownerOfFine(
coords[idx]);
720 if (owner == rank_) {
723 out[idx] = field[
static_cast<std::size_t
>(
leaf)];
725 appendRequest(
req[owner],
coords[idx],
static_cast<std::int64_t
>(idx));
730 [&](
const std::array<Coord, Dim>& gc) ->
double {
739 enum NbState { InBlock, Remote, DomainNone };
741 int ownerOfFine(
const std::array<Coord, Dim>& gc)
const {
743 for (
int d = 0; d < Dim; ++d)
751 template <
class Responder>
753 std::vector<double>&
out,
int tagA,
int tagB)
const {
754 std::map<int, std::vector<char>>
rep;
756 halo::NbxEngine
eng(comm_);
757 auto it =
req.begin();
759 [&](std::vector<char>&
o) ->
int {
767 [&](
int src, std::vector<char>&
msg) {
768 parseRequests(
msg, [&](
const std::array<Coord, Dim>& gc, std::int64_t
reqId) {
775 halo::NbxEngine
eng(comm_);
776 auto it =
rep.begin();
778 [&](std::vector<char>&
o) ->
int {
786 [&](
int, std::vector<char>&
msg) {
787 parseReplies(
msg, [&](std::int64_t
reqId,
double v) {
788 out[
static_cast<std::size_t
>(
reqId)] =
v;
795 static int faceSlot(
Index i,
int axis,
int dir,
int F) {
796 return static_cast<int>(
i) *
F + 2 * axis + (dir > 0 ? 0 : 1);
802 NbState neighborInfo(
Index i,
int axis,
int dir, std::array<Coord, Dim>& gc,
int& owner,
803 Index& localNb)
const {
805 const auto&
lo = b[0];
807 const long pc = (dir > 0) ?
static_cast<long>(
lo[axis]) +
static_cast<long>(
size)
810 if (
pc >= 0 &&
pc <
static_cast<long>(blockFineSize_[axis])) {
811 std::array<Coord, Dim>
p =
lo;
813 localNb = local_.
find(M::encode(
p).code());
817 std::array<long, Dim> g{};
818 for (
int d = 0; d < Dim; ++d)
819 g[d] =
static_cast<long>(blockFineOrigin_[d]) +
static_cast<long>(
lo[d]);
820 g[axis] =
static_cast<long>(blockFineOrigin_[axis]) +
pc;
821 for (
int d = 0; d < Dim; ++d) {
822 const long gf =
static_cast<long>(globalFineSize_[d]);
823 if (g[d] < 0 || g[d] >=
gf) {
826 g[d] = ((g[d] %
gf) +
gf) %
gf;
830 for (
int d = 0; d < Dim; ++d) {
831 gc[d] =
static_cast<Coord>(g[d]);
832 rootCell[d] =
static_cast<Index>(g[d] /
static_cast<long>(rootSpan_));
840 Index locateGlobal(
const std::array<Coord, Dim>& gc)
const {
841 std::array<Coord, Dim>
lc{};
842 for (
int d = 0; d < Dim; ++d)
843 lc[d] =
static_cast<Coord>(gc[d] - blockFineOrigin_[d]);
844 return local_.
find(M::encode(
lc).code());
848 static void appendBytes(std::vector<char>&
buf,
const void*
p, std::size_t n) {
849 const char*
c =
static_cast<const char*
>(
p);
852 static void appendQuery(std::vector<char>&
buf,
const std::array<Coord, Dim>& gc,
int Lf) {
853 for (
int d = 0; d < Dim; ++d) {
854 std::int64_t
v =
static_cast<std::int64_t
>(gc[d]);
855 appendBytes(
buf, &
v,
sizeof(
v));
858 appendBytes(
buf, &
l,
sizeof(
l));
861 static void parseQueries(
const std::vector<char>&
buf,
Fn&&
fn) {
863 const std::size_t
item = Dim *
sizeof(std::int64_t) +
sizeof(std::int32_t);
864 while (off +
item <=
buf.size()) {
865 std::array<Coord, Dim> gc{};
866 for (
int d = 0; d < Dim; ++d) {
868 std::memcpy(&
v,
buf.data() + off,
sizeof(
v));
870 gc[d] =
static_cast<Coord>(
v);
873 std::memcpy(&
l,
buf.data() + off,
sizeof(
l));
875 fn(gc,
static_cast<int>(
l));
878 static void appendRequest(std::vector<char>&
buf,
const std::array<Coord, Dim>& gc,
879 std::int64_t
reqId) {
880 for (
int d = 0; d < Dim; ++d) {
881 std::int64_t
v =
static_cast<std::int64_t
>(gc[d]);
882 appendBytes(
buf, &
v,
sizeof(
v));
887 static void parseRequests(
const std::vector<char>&
buf,
Fn&&
fn) {
889 const std::size_t
item = (Dim + 1) *
sizeof(std::int64_t);
890 while (off +
item <=
buf.size()) {
891 std::array<Coord, Dim> gc{};
892 for (
int d = 0; d < Dim; ++d) {
894 std::memcpy(&
v,
buf.data() + off,
sizeof(
v));
896 gc[d] =
static_cast<Coord>(
v);
900 off +=
sizeof(
reqId);
906 static void appendLeaf(std::vector<char>&
buf,
Code gc, std::uint8_t level,
907 const std::vector<std::vector<double>>&
fields,
Index i,
int K) {
908 std::int64_t
c =
static_cast<std::int64_t
>(gc);
909 appendBytes(
buf, &
c,
sizeof(
c));
910 std::int32_t
l =
static_cast<std::int32_t
>(level);
911 appendBytes(
buf, &
l,
sizeof(
l));
912 for (
int k = 0; k <
K; ++k) {
913 double v =
fields[
static_cast<std::size_t
>(k)][
static_cast<std::size_t
>(
i)];
914 appendBytes(
buf, &
v,
sizeof(
v));
918 static void parseLeaves(
const std::vector<char>&
buf,
int K,
Fn&&
fn) {
920 const std::size_t
item =
921 sizeof(std::int64_t) +
sizeof(std::int32_t) +
static_cast<std::size_t
>(
K) *
sizeof(
double);
922 std::vector<double>
comps(
static_cast<std::size_t
>(
K));
923 while (off +
item <=
buf.size()) {
925 std::memcpy(&
c,
buf.data() + off,
sizeof(
c));
928 std::memcpy(&
l,
buf.data() + off,
sizeof(
l));
930 for (
int k = 0; k <
K; ++k) {
931 std::memcpy(&
comps[
static_cast<std::size_t
>(k)],
buf.data() + off,
sizeof(
double));
934 fn(
static_cast<Code>(
c),
static_cast<std::uint8_t
>(
l),
comps.data());
937 static void appendReply(std::vector<char>&
buf, std::int64_t
reqId,
double v) {
939 appendBytes(
buf, &
v,
sizeof(
v));
942 static void parseReplies(
const std::vector<char>&
buf,
Fn&&
fn) {
944 const std::size_t
item =
sizeof(std::int64_t) +
sizeof(
double);
945 while (off +
item <=
buf.size()) {
948 off +=
sizeof(
reqId);
950 std::memcpy(&
v,
buf.data() + off,
sizeof(
v));
959 decomp::BlockDecomposer<Dim> dec_;
968 std::array<bool, Dim> periodic_{};
971 int rebalanceAlign_ = 0;
972 double rebalanceImbalance_ = 0.0;
Index balance2to1()
Enforce 2:1 (graded) balance within this block: no two face-adjacent leaves differ by more than one 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...
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...
morton::Morton< Dim, Bits > M
void liftRoot()
Lift the root one level: halve the root brick (and the block's global root origin) on every axis and ...
unsigned level(Index i) const
Index find(Code p) const
Leaf containing Morton code p, or -1. Host wrapper over amrLocate.
void init(IVec< Dim > brick, unsigned lmax, IVec< Dim > globalOrigin=IVec< Dim >{})
std::array< std::array< Coord, Dim >, 2 > bounds(Index i) const
Inclusive integer bounds [lo, hi] of leaf i in fine units.
typename M::coord_type Coord
typename M::code_type Code
bool canLiftRoot() const
True iff the root brick can be halved on every axis: brick() and globalOrigin() both even everywhere.
typename Octree::Coord Coord
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.
DistributedOctree()=default
const Octree & local() const
FaceGatherPlan buildFaceGatherPlan() const
const IVec< Dim > & globalRootSize() const
static constexpr double kNoNeighbor
gather sentinel for "no neighbour"
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...
Index balance()
Bring the whole distributed octree to a 2:1-balanced state.
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 ...
const Vec< Dim > & h0() const
Phase 3: the FINEST spacing per axis (docs/amr_anisotropic.md §2).
const AmrGeometry< Dim > & globalGeometry() const
AmrGeometry< Dim > localGeometry() const
World geometry of this rank's block (the global geometry shifted to its origin).
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).
const IVec< Dim > & blockFineOrigin() const
FaceInfo faceAcross(Index i, int axis, int dir) const
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...
typename Octree::Code Code
const std::array< bool, Dim > & periodic() const
void initDecomposed(const decomp::BlockDecomposer< Dim > &dec, unsigned lmax, AmrGeometry< Dim > globalGeo, std::array< bool, Dim > periodic, MPI_Comm comm)
As init, but on a GIVEN decomposition of the global root grid dec.globalSize() instead of the proport...
void liftRoot()
Lift the root one level: the root CELL doubles on every axis, so the global root grid,...
const IVec< Dim > & globalFineSize() const
int rebalanceAlignment() const
The alignment exponent the last rebalance chose (align = 2^a on every axis; 0 = the plain weighted OR...
Index findGlobalRoot(const IVec< Dim > &g) const
Local leaf covering global root-cell g, or -1 if not owned by this rank (lmax==0).
BlockOctree< Dim, Bits > Octree
const decomp::BlockDecomposer< Dim > & decomposition() const
The ORB over the global ROOT grid in the CURRENT root units — rank r owns block r.
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,...
double h0Scalar() const
The single spacing of a CUBIC global grid.
bool canLiftRoot() const
True iff THIS rank's block nests one level deeper: the global root grid, this block's origin and this...
Code globalCode(Index i) const
Global Morton code of local leaf i (origin in global fine coordinates).
GatherHaloTopology buildGatherHaloTopology(const FaceGatherPlan &plan) const
Build the value-only gather topology from a FaceGatherPlan: classify each remote coord by owner (owne...
const IVec< Dim > & blockBrick() const
IVec< Dim > globalRootOf(Index i) const
Global root-cell coordinate of local leaf i (lmax==0: the leaf is one root cell).
double rebalanceImbalance() const
const IVec< Dim > & blockOriginRoot() const
std::vector< std::array< double, Dim > > transferFieldGradients(const BlockOctree< Dim, Bits > &oldT, const std::vector< double > &oldF)
Per-old-leaf minmod prolongation gradients (per fine-coordinate unit) — transferField's stencil,...
World-space placement of a block-local octree: fine coordinate (0,..,0) sits at origin,...
Static topology of the face-neighbour gather: which out-slots are filled from a local leaf (directSlo...
std::vector< Index > remoteSlot
out[remoteSlot[k]] = covered value
std::vector< Index > directLeaf
out[directSlot[k]] = field[directLeaf[k]]
std::vector< Index > directSlot
Index nFaces
local leaves * 2*Dim
std::vector< std::array< Coord, Dim > > remoteCoords
owner-gathered coords (incl. owner==rank)
Classification of leaf i's face on (axis,dir): {state, global fine probe gc, owner,...
std::array< Coord, Dim > gc
Flattened, value-only topology for the face-neighbour gather, established ONCE: the per-matvec exchan...
std::vector< int > sendRanks
std::vector< int > recvCounts
std::vector< int > sendCounts
std::vector< Index > recvSlot
std::vector< int > recvRanks
std::vector< Index > localSlot
std::vector< Index > localLeaf
std::vector< Index > sendLeaf