core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
block_decomposer.hpp
Go to the documentation of this file.
1// core — orthogonal recursive bisection (ORB) domain decomposition.
2//
3// Ported from block_decomposer/src/BlockDecomposer.hpp (pbs::BlockDecomposer), modernized into the
4// peclet::core namespace. The global cell grid is split recursively along its largest axis into
5// `numBlocks` rank-owned blocks. Adds ownerOf() (a tree walk) for halo topology construction.
6#ifndef PECLET_CORE_DECOMP_BLOCK_DECOMPOSER_HPP
7#define PECLET_CORE_DECOMP_BLOCK_DECOMPOSER_HPP
8
9#include <array>
10#include <cassert>
11#include <cmath>
12#include <cstddef>
13#include <limits>
14#include <stack>
15#include <vector>
16
18
20
22template <int Dim>
27
32template <int Dim>
34 public:
35 BlockDecomposer() = default;
39 BlockDecomposer(std::size_t numBlocks, IVec<Dim> globalSize, const std::vector<Real>& weights) {
41 }
46
49 void init(std::size_t numBlocks, IVec<Dim> globalSize) {
50 align_ = IVec<Dim>{};
51 for (int i = 0; i < Dim; ++i)
52 align_[i] = 1;
53 initImpl(numBlocks, globalSize, nullptr);
54 }
55
62 void init(std::size_t numBlocks, IVec<Dim> globalSize, const IVec<Dim>& align) {
63 align_ = align;
64 for (int i = 0; i < Dim; ++i)
65 if (align_[i] < 1)
66 align_[i] = 1;
67 initImpl(numBlocks, globalSize, nullptr);
68 }
69
77 void init(std::size_t numBlocks, IVec<Dim> globalSize, const IVec<Dim>& align,
78 const IVec<Dim>& cellExtent) {
79 cellExtent_ = cellExtent;
81 }
82
88 void init(std::size_t numBlocks, IVec<Dim> globalSize, const std::vector<Real>& weights) {
89 assert(weights.size() == static_cast<std::size_t>([&] {
90 Index v = 1;
91 for (int i = 0; i < Dim; ++i)
92 v *= globalSize[i];
93 return v;
94 }()) &&
95 "weights array must cover the global grid (x-fastest)");
96 align_ = IVec<Dim>{};
97 for (int i = 0; i < Dim; ++i)
98 align_[i] = 1;
99 initImpl(numBlocks, globalSize, &weights);
100 }
101
124 void init(std::size_t numBlocks, IVec<Dim> globalSize, const std::vector<Real>& weights,
125 const IVec<Dim>& align) {
126 IVec<Dim> gc{};
127 Index nc = 1;
128 for (int k = 0; k < Dim; ++k) {
129 assert(align[k] >= 1 && "aligned weighted init(): align must be >= 1");
130 assert(globalSize[k] % align[k] == 0 &&
131 "aligned weighted init(): globalSize must be a multiple of align");
132 gc[k] = globalSize[k] / align[k];
133 nc *= gc[k];
134 }
135 // The coarse weight field. The fine grid is walked x-fastest, so every coarse cell receives
136 // its align-box in the box's own x-fastest order; the first cell of a box ASSIGNS (a one-term
137 // sum is then a bitwise copy), the rest accumulate. Deterministic, hence replicated.
138 std::vector<Real> wc(static_cast<std::size_t>(nc), 0.0);
139 BlockDecomposer<Dim> fineIndex; // x-fastest indexing on the fine grid only
140 fineIndex.globalSize_ = globalSize;
142 coarse.globalSize_ = gc;
143 assert(weights.size() == static_cast<std::size_t>([&] {
144 Index v = 1;
145 for (int i = 0; i < Dim; ++i)
146 v *= globalSize[i];
147 return v;
148 }()) &&
149 "weights array must cover the global grid (x-fastest)");
151 IVec<Dim> c{};
152 bool first = true;
153 for (int k = 0; k < Dim; ++k) {
154 c[k] = g[k] / align[k];
155 if (g[k] % align[k] != 0)
156 first = false;
157 }
158 const std::size_t ic = static_cast<std::size_t>(coarse.linearGlobal(c));
159 const Real wv = weights[static_cast<std::size_t>(fineIndex.linearGlobal(g))];
160 if (first)
161 wc[ic] = wv;
162 else
163 wc[ic] += wv;
164 });
165 coarse.cellExtent_ = align;
166 coarse.init(numBlocks, gc, wc); // the existing weighted ORB (sets align_ = 1)
167 *this = coarse.refined(align); // the existing exact inverse of coarsened(); align_ = align
168 }
169
171 std::size_t numBlocks() const { return origins_.size(); }
173 const IVec<Dim>& globalSize() const { return globalSize_; }
175 const std::vector<IVec<Dim>>& origins() const { return origins_; }
177 const std::vector<IVec<Dim>>& sizes() const { return sizes_; }
178
180 Block<Dim> block(std::size_t b) const { return {origins_[b], sizes_[b]}; }
181
183 int ownerOf(const IVec<Dim>& g) const {
184 Index node = 0;
185 while (tree_[node].splitDim != -1) {
186 node = (g[tree_[node].splitDim] < tree_[node].splitValue) ? 2 * node + 1 : 2 * node + 2;
187 }
188 return static_cast<int>(tree_[node].splitValue);
189 }
190
196 void flattenTree(std::vector<int>& splitDim, std::vector<Index>& splitVal) const {
197 splitDim.resize(tree_.size());
198 splitVal.resize(tree_.size());
199 for (std::size_t i = 0; i < tree_.size(); ++i) {
200 splitDim[i] = tree_[i].splitDim;
201 splitVal[i] = tree_[i].splitValue;
202 }
203 }
204
206 Index linearGlobal(const IVec<Dim>& g) const {
207 Index idx = g[Dim - 1];
208 for (int i = Dim - 2; i >= 0; --i) {
209 idx *= globalSize_[i];
210 idx += g[i];
211 }
212 return idx;
213 }
214
217 IVec<Dim> g{};
218 for (int i = 0; i < Dim; ++i) {
219 g[i] = lin % globalSize_[i];
220 lin /= globalSize_[i];
221 }
222 return g;
223 }
224
233 for (int k = 0; k < Dim; ++k) {
234 assert(ratio[k] >= 1 && globalSize_[k] % ratio[k] == 0 &&
235 "coarsened(): global size not divisible by ratio (build the decomposition aligned)");
236 c.globalSize_[k] = globalSize_[k] / ratio[k];
237 c.align_[k] = align_[k] / ratio[k] >= 1 ? align_[k] / ratio[k] : 1;
238 }
239 c.tree_ = tree_;
240 for (auto& nd : c.tree_)
241 if (nd.splitDim != -1) {
242 assert(nd.splitValue % ratio[nd.splitDim] == 0 &&
243 "coarsened(): split not divisible by ratio (build the decomposition aligned)");
244 nd.splitValue /= ratio[nd.splitDim];
245 }
246 c.origins_.resize(origins_.size());
247 c.sizes_.resize(sizes_.size());
248 for (std::size_t b = 0; b < origins_.size(); ++b)
249 for (int k = 0; k < Dim; ++k) {
250 assert(origins_[b][k] % ratio[k] == 0 && sizes_[b][k] % ratio[k] == 0 &&
251 "coarsened(): block origin/size not divisible by ratio (build aligned)");
252 c.origins_[b][k] = origins_[b][k] / ratio[k];
253 c.sizes_[b][k] = sizes_[b][k] / ratio[k];
254 }
255 return c;
256 }
257
274 for (int k = 0; k < Dim; ++k) {
275 assert(ratio[k] >= 1 && "refined(): ratio must be >= 1");
276 f.globalSize_[k] = globalSize_[k] * ratio[k];
277 f.align_[k] = align_[k] * ratio[k];
278 f.cellExtent_[k] = 1; // the refined grid's cells are the fine cells
279 }
280 f.tree_ = tree_;
281 for (auto& nd : f.tree_)
282 if (nd.splitDim != -1)
283 nd.splitValue *= ratio[nd.splitDim];
284 f.origins_.resize(origins_.size());
285 f.sizes_.resize(sizes_.size());
286 for (std::size_t b = 0; b < origins_.size(); ++b)
287 for (int k = 0; k < Dim; ++k) {
288 f.origins_[b][k] = origins_[b][k] * ratio[k];
289 f.sizes_[b][k] = sizes_[b][k] * ratio[k];
290 }
291 return f;
292 }
293
294 private:
295 struct TreeNode {
296 int splitDim = -1;
297 Index splitValue = 0;
298 };
299
300 public:
303 int treeDepth() const {
304 int best = 0;
305 depthWalk(0, 0, best);
306 return best;
307 }
308
321 BlockDecomposer<Dim> agglomerated(int depth, std::vector<int>* groupOf = nullptr,
322 std::vector<int>* rootOf = nullptr) const {
323 assert(depth >= 0 && "agglomerated(): depth must be >= 0");
325 a.globalSize_ = globalSize_;
326 a.align_ = align_;
327 a.cellExtent_ = cellExtent_;
328 a.tree_.assign(tree_.size(), TreeNode{}); // everything a leaf until visited
329 std::vector<int> gmap(origins_.size(), -1);
330 std::vector<int> roots;
331 agglomWalk(0, 0, depth, IVec<Dim>{}, globalSize_, a, gmap, roots);
332 a.tree_.resize(prunedSize(a.tree_));
333 if (groupOf)
334 *groupOf = gmap;
335 if (rootOf)
336 *rootOf = roots;
337 return a;
338 }
339
340 private:
341 void depthWalk(Index node, int d, int& best) const {
342 if (node >= static_cast<Index>(tree_.size()))
343 return;
344 if (tree_[node].splitDim == -1) {
345 if (d > best)
346 best = d;
347 return;
348 }
349 depthWalk(2 * node + 1, d + 1, best);
350 depthWalk(2 * node + 2, d + 1, best);
351 }
352 // Collect the (in-order) original leaf indices under `node`.
353 void leavesUnder(Index node, std::vector<int>& out) const {
354 if (tree_[node].splitDim == -1) {
355 out.push_back(static_cast<int>(tree_[node].splitValue));
356 return;
357 }
358 leavesUnder(2 * node + 1, out);
359 leavesUnder(2 * node + 2, out);
360 }
361 void agglomWalk(Index node, int d, int depth, IVec<Dim> origin, IVec<Dim> size,
362 BlockDecomposer<Dim>& a, std::vector<int>& gmap, std::vector<int>& roots) const {
363 const TreeNode& nd = tree_[node];
364 if (nd.splitDim == -1 || d == depth) {
365 // becomes a block of the agglomerated decomposition
366 const int newIdx = static_cast<int>(a.origins_.size());
367 a.tree_[node] = TreeNode{-1, newIdx};
368 a.origins_.push_back(origin);
369 a.sizes_.push_back(size);
370 std::vector<int> leaves;
371 leavesUnder(node, leaves);
372 int root = leaves.front();
373 for (int b : leaves) {
374 gmap[b] = newIdx;
375 if (b < root)
376 root = b;
377 }
378 roots.push_back(root);
379 return;
380 }
381 a.tree_[node] = nd; // kept internal node: same split
382 IVec<Dim> lsize = size, rorigin = origin, rsize = size;
383 lsize[nd.splitDim] = nd.splitValue - origin[nd.splitDim];
384 rorigin[nd.splitDim] = nd.splitValue;
385 rsize[nd.splitDim] = origin[nd.splitDim] + size[nd.splitDim] - nd.splitValue;
386 agglomWalk(2 * node + 1, d + 1, depth, origin, lsize, a, gmap, roots);
387 agglomWalk(2 * node + 2, d + 1, depth, rorigin, rsize, a, gmap, roots);
388 }
389 static std::size_t prunedSize(const std::vector<TreeNode>& t) {
390 // keep up to the last node that is reachable (an internal node or a numbered leaf); leaves
391 // default to {-1, 0} which is indistinguishable from block 0, so keep by reachability instead
392 std::size_t last = 0;
393 std::vector<Index> stack{0};
394 while (!stack.empty()) {
395 const Index n = stack.back();
396 stack.pop_back();
397 if (n >= static_cast<Index>(t.size()))
398 continue;
399 if (static_cast<std::size_t>(n) > last)
400 last = static_cast<std::size_t>(n);
401 if (t[n].splitDim != -1) {
402 stack.push_back(2 * n + 1);
403 stack.push_back(2 * n + 2);
404 }
405 }
406 return last + 1;
407 }
408
409 private:
412 void initImpl(std::size_t numBlocks, IVec<Dim> globalSize, const std::vector<Real>* weights);
413
418 Index splitPosition(const IVec<Dim>& origin, const IVec<Dim>& size, int kLargest,
419 std::size_t numSub, std::size_t numTotal,
420 const std::vector<Real>* weights) const;
421
422 IVec<Dim> globalSize_{};
424 align_{};
425 IVec<Dim> cellExtent_{};
426 std::vector<IVec<Dim>> origins_;
427 std::vector<IVec<Dim>> sizes_;
428 std::vector<TreeNode> tree_;
429};
430
431template <int Dim>
432void BlockDecomposer<Dim>::initImpl(std::size_t numBlocks, IVec<Dim> globalSize,
433 const std::vector<Real>* weights) {
434 globalSize_ = globalSize;
435 for (int k = 0; k < Dim; ++k)
436 if (cellExtent_[k] <= 0)
437 cellExtent_[k] = 1;
438 origins_.clear();
439 sizes_.clear();
440 tree_.clear();
441
442 struct StackBlock {
443 IVec<Dim> origin;
444 IVec<Dim> size;
445 std::size_t numSub;
446 Index treeIndx;
447 };
448 std::stack<StackBlock> stack;
449 stack.push(StackBlock{IVec<Dim>{}, globalSize, numBlocks, 0});
450
451 Index leafIndex = 0;
452 while (!stack.empty()) {
453 StackBlock cur = stack.top();
454 stack.pop();
455
456 if (static_cast<std::size_t>(cur.treeIndx) >= tree_.size()) {
457 tree_.resize(cur.treeIndx + 1);
458 }
459
460 if (cur.numSub > 1) {
461 // Split along the largest axis. The split position balances either the sub-block cell count
462 // (unweighted) or the cumulative weight (weighted) of the two children.
463 // Compare PHYSICAL extents: size*cellExtent. cellExtent is all-ones for an ordinary grid, so
464 // this is the plain largest-cell-count test unless an anisotropically coarsened grid says
465 // otherwise (see the cellExtent init).
466 int kLargest = 0;
467 for (int k = 1; k < Dim; ++k) {
468 if (cur.size[k] * cellExtent_[k] > cur.size[kLargest] * cellExtent_[kLargest])
469 kLargest = k;
470 }
471 std::size_t numSub = cur.numSub / 2;
472 Index szSub = splitPosition(cur.origin, cur.size, kLargest, numSub, cur.numSub, weights);
473
474 // Snap the split to a multiple of align_[kLargest] so the global split position
475 // (cur.origin[kLargest] + szSub, with cur.origin already aligned) is a multiple too — the
476 // precondition for coarsened() to divide cleanly. Skip if the box is too small to split
477 // aligned.
478 const Index a = align_[kLargest];
479 if (a > 1 && cur.size[kLargest] >= 2 * a) {
480 szSub = ((szSub + a / 2) / a) * a;
481 if (szSub < a)
482 szSub = a;
483 if (szSub > cur.size[kLargest] - a)
484 szSub = cur.size[kLargest] - a;
485 }
486
488 left.numSub = numSub;
489 left.size[kLargest] = szSub;
490 left.treeIndx = 2 * cur.treeIndx + 1;
491
493 right.origin[kLargest] += szSub;
494 right.size[kLargest] -= szSub;
495 right.numSub = cur.numSub - numSub;
496 right.treeIndx = 2 * cur.treeIndx + 2;
497
498 tree_[cur.treeIndx] = TreeNode{kLargest, cur.origin[kLargest] + szSub};
499 stack.push(right);
500 stack.push(left);
501 } else {
502 tree_[cur.treeIndx] = TreeNode{-1, leafIndex++};
503 origins_.push_back(cur.origin);
504 sizes_.push_back(cur.size);
505 }
506 }
507}
508
509template <int Dim>
510Index BlockDecomposer<Dim>::splitPosition(const IVec<Dim>& origin, const IVec<Dim>& size,
511 int kLargest, std::size_t numSub, std::size_t numTotal,
512 const std::vector<Real>* weights) const {
513 const Index n = size[kLargest];
514
515 // Unweighted (and the degenerate non-splittable axis): the classic proportional-to-count split.
516 // Kept as the exact same expression so the unweighted API is byte-for-byte unchanged.
517 if (weights == nullptr || n <= 1) {
518 return static_cast<Index>(std::round(static_cast<double>(n) * static_cast<double>(numSub) /
519 static_cast<double>(numTotal)));
520 }
521
522 // Weighted: accumulate the weight of each slab (fixed kLargest-coordinate) within the box, then
523 // pick the boundary whose cumulative weight is closest to the target fraction of the total.
524 std::vector<double> slab(static_cast<std::size_t>(n), 0.0);
525 IVec<Dim> bgn = origin, end{};
526 for (int i = 0; i < Dim; ++i)
527 end[i] = origin[i] + size[i];
528 forEachInBox<Dim>(bgn, end, [&](const IVec<Dim>& g) {
529 slab[static_cast<std::size_t>(g[kLargest] - origin[kLargest])] += (*weights)[linearGlobal(g)];
530 });
531
532 double total = 0.0;
533 for (double w : slab)
534 total += w;
535 const double target = total * static_cast<double>(numSub) / static_cast<double>(numTotal);
536
537 // Search boundaries in [1, n-1] (non-empty children). Ties resolve to the larger boundary, which
538 // mirrors std::round's half-away-from-zero rule so equal weights reproduce the unweighted split.
539 double cum = 0.0;
540 double bestDist = std::numeric_limits<double>::max();
541 Index best = 1;
542 for (Index s = 1; s < n; ++s) {
543 cum += slab[static_cast<std::size_t>(s - 1)];
544 const double dist = std::abs(cum - target);
545 if (dist <= bestDist) {
546 bestDist = dist;
547 best = s;
548 }
549 }
550 return best;
551}
552
559template <int Dim>
560Real weightImbalance(const BlockDecomposer<Dim>& dec, const std::vector<Real>& weights) {
561 const std::size_t nb = dec.numBlocks();
562 double total = 0.0, hi = 0.0;
563 for (std::size_t b = 0; b < nb; ++b) {
564 IVec<Dim> bgn = dec.origins()[b], end{};
565 for (int k = 0; k < Dim; ++k)
566 end[k] = bgn[k] + dec.sizes()[b][k];
567 double w = 0.0;
568 forEachInBox<Dim>(bgn, end, [&](const IVec<Dim>& g) {
569 w += weights[static_cast<std::size_t>(dec.linearGlobal(g))];
570 });
571 total += w;
572 if (b == 0 || w > hi)
573 hi = w;
574 }
575 if (!(total > 0.0) || nb == 0)
576 return std::numeric_limits<Real>::infinity();
577 return hi / (total / static_cast<double>(nb));
578}
579
582template <int Dim>
588
608 const std::array<Index, N>& G,
609 const std::vector<Real>& weights,
610 Real budget = 1.05,
611 int aMax = std::numeric_limits<int>::max()) {
612 int cap = aMax;
613 for (int k = 0; k < Dim; ++k) {
614 int tz = 0;
615 for (Index g = G[k]; g > 0 && g % 2 == 0; g /= 2)
616 ++tz;
617 cap = tz < cap ? tz : cap;
618 }
619 int top = 0; // the largest a <= cap whose coarse grid still holds numBlocks, >= 2 cells per axis
620 while (top < cap) {
621 const int a = top + 1;
622 bool ok = true;
623 double cells = 1.0;
624 for (int k = 0; k < Dim; ++k) {
625 const Index gk = G[k] >> a;
626 if (gk < 2)
627 ok = false;
628 cells *= static_cast<double>(gk);
629 }
630 if (!ok || cells < static_cast<double>(numBlocks))
631 break;
632 top = a;
633 }
634 for (int a = top; a >= 1; --a) {
636 for (int k = 0; k < Dim; ++k)
637 align[k] = Index(1) << a;
639 c.dec.init(numBlocks, G, weights, align);
640 bool empty = false;
641 for (const auto& sz : c.dec.sizes())
642 for (int k = 0; k < Dim; ++k)
643 if (sz[k] <= 0)
644 empty = true;
645 if (empty)
646 continue;
648 if (c.imbalance <= budget) {
649 c.a = a;
650 return c;
651 }
652 }
654 c.dec.init(numBlocks, G, weights);
655 c.a = 0;
657 return c;
658}
659
660} // namespace peclet::core::decomp
661
662#endif // PECLET_CORE_DECOMP_BLOCK_DECOMPOSER_HPP
Orthogonal recursive bisection of a Dim-dimensional global cell grid into numBlocks axis-aligned bloc...
void flattenTree(std::vector< int > &splitDim, std::vector< Index > &splitVal) const
Flatten the implicit ORB tree into two parallel arrays for a device-callable ownerOf: for node i,...
Block< Dim > block(std::size_t b) const
Block b as (origin, size). Precondition: b < numBlocks() (unchecked).
const std::vector< IVec< Dim > > & sizes() const
Extent in cells of every block, indexed by block.
int treeDepth() const
Depth of the deepest leaf of the ORB tree (root = 0).
void init(std::size_t numBlocks, IVec< Dim > globalSize, const IVec< Dim > &align, const IVec< Dim > &cellExtent)
Anisotropic-cell aligned ORB: cellExtent[k] is how many underlying FINE cells one cell of this grid s...
void init(std::size_t numBlocks, IVec< Dim > globalSize)
Build the decomposition of a globalSize cell grid into numBlocks blocks (equal cell count).
BlockDecomposer(std::size_t numBlocks, IVec< Dim > globalSize, const std::vector< Real > &weights)
Weighted ORB: see init(numBlocks, globalSize, weights).
const std::vector< IVec< Dim > > & origins() const
Inclusive lower corner of every block, in global cell coordinates, indexed by block.
const IVec< Dim > & globalSize() const
Global grid size in cells per axis.
BlockDecomposer< Dim > agglomerated(int depth, std::vector< int > *groupOf=nullptr, std::vector< int > *rootOf=nullptr) const
Coarse-level TELESCOPING: the decomposition obtained by merging ORB siblings — truncate the bisection...
void init(std::size_t numBlocks, IVec< Dim > globalSize, const IVec< Dim > &align)
Aligned ORB: force every split position (and hence every block origin/size) on axis k to be a multipl...
void init(std::size_t numBlocks, IVec< Dim > globalSize, const std::vector< Real > &weights)
Weighted ORB: balance the total weight per block instead of the cell count.
std::size_t numBlocks() const
Number of blocks.
BlockDecomposer(std::size_t numBlocks, IVec< Dim > globalSize)
Equal-cell-count ORB: see init(numBlocks, globalSize).
Index linearGlobal(const IVec< Dim > &g) const
Global multi-index -> global linear index (x-fastest: I = x + y*nx + z*nx*ny).
BlockDecomposer< Dim > refined(const IVec< Dim > &ratio) const
Derive the NESTED fine decomposition: the exact inverse of coarsened() — global size,...
BlockDecomposer(std::size_t numBlocks, IVec< Dim > globalSize, const IVec< Dim > &align)
Aligned ORB (coarsenable): see the aligned init() below.
int ownerOf(const IVec< Dim > &g) const
Owning block index of a global cell coordinate. Caller must wrap into [0, globalSize) first.
BlockDecomposer< Dim > coarsened(const IVec< Dim > &ratio) const
Derive the NESTED coarse decomposition: each block, split value and the global size divided by ratio ...
IVec< Dim > multiGlobal(Index lin) const
Global linear index -> global multi-index (inverse of linearGlobal).
void init(std::size_t numBlocks, IVec< Dim > globalSize, const std::vector< Real > &weights, const IVec< Dim > &align)
Aligned weighted ORB — COARSE-FIRST, never snap-after (amr/docs/amr_mg_core_boundary....
Real weightImbalance(const BlockDecomposer< Dim > &dec, const std::vector< Real > &weights)
Weight imbalance of a decomposition — the balancer's own metric: the heaviest block's total weight ov...
AlignedWeightedChoice< Dim > chooseAlignedWeighted(std::size_t numBlocks, const std::array< Index, N > &G, const std::vector< Real > &weights, Real budget=1.05, int aMax=std::numeric_limits< int >::max())
Choose the alignment of a weighted ORB from an imbalance budget (amr/docs/ amr_mg_core_boundary....
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
What chooseAlignedWeighted returns: the partition, the alignment exponent a (align = 2^a on every axi...
Real imbalance
weightImbalance(dec, weights)
BlockDecomposer< Dim > dec
the chosen partition
int a
alignment exponent: align = 2^a on every axis (0 = unaligned)
A rank-owned axis-aligned block of the global cell grid.
IVec< Dim > size
extent in cells along each axis
IVec< Dim > origin
inclusive lower corner in global cell coordinates