core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
stage_target.hpp
Go to the documentation of this file.
1// core — the TARGET of a coarse-level multigrid stage: onto which decomposition of a level's own
2// grid the level moves when it can no longer coarsen in place (suite decision "Coarse-level
3// redistribution lives in core; the hierarchies stay in the methods", amr/docs/
4// amr_mg_core_boundary.md §4).
5//
6// A pure function of the current decomposition and the METHOD's lift predicate, replicated on every
7// rank with no communication. Core does not know what makes a decomposition coarsenable — flow's
8// rule is per-axis (every block even in origin and size on each axis whose global extent can still
9// halve), amr's is all-axes — so the caller supplies it as `liftable(dec)`.
10//
11// The kinds:
12// InPlace — no stage: the level coarsens on its current decomposition.
13// SiblingMerge — the ORB tree truncated (`BlockDecomposer::agglomerated(d)`): each target block
14// is the union of a contiguous run of current blocks, owned by the lowest of their
15// ranks. Moved by a group gather / scatter (stage_comm.hpp,
16// redistribute_topology.hpp).
17// Repartition — a fresh proportional ORB of the level grid on the first np_L parent ranks
18// (repartitionTarget, §11.2 of the design). Moved point-to-point over the box
19// intersections of the two decompositions (redistribute_topology.hpp).
20// Replicated — the whole level as one block on EVERY rank. Moved by an Allgatherv.
21//
22// With `maxBlockCells == 0` the policy is flow's telescope search verbatim
23// (flow/src/mac_cutcell_mg.hpp, CutcellMG::initMpi): so that flow's migration onto it is
24// byte-identical. `maxBlockCells > 0` (design §11.1) bounds the block a stage may hand a rank.
25#ifndef PECLET_CORE_DECOMP_STAGE_TARGET_HPP
26#define PECLET_CORE_DECOMP_STAGE_TARGET_HPP
27
28#include <cstddef>
29#include <limits>
30#include <optional>
31#include <stdexcept>
32#include <type_traits>
33#include <utility>
34#include <vector>
35
38
39namespace peclet::core::decomp {
40
43enum class StageKind {
44 InPlace,
48};
49
52template <int Dim>
66
68template <int Dim>
70 Index m = std::numeric_limits<Index>::max();
71 for (const auto& s : d.sizes())
72 for (int k = 0; k < Dim; ++k)
73 m = s[k] < m ? s[k] : m;
74 return m;
75}
76
78template <int Dim>
80 Index m = 0;
81 for (const auto& s : d.sizes()) {
82 Index c = 1;
83 for (int k = 0; k < Dim; ++k)
84 c *= s[k];
85 m = c > m ? c : m;
86 }
87 return m;
88}
89
92template <int Dim>
94 int depth = -1;
96 std::vector<int> groupOf;
97 std::vector<int> ownerOf;
98};
99
114template <int Dim, class Liftable>
115std::optional<SiblingMergeChoice<Dim>> shallowestLiftableMerge(const BlockDecomposer<Dim>& cur,
116 Liftable&& liftable, int minExtent) {
117 for (int d = cur.treeDepth() - 1; d >= 0; --d) {
119 m.depth = d;
120 m.dec = cur.agglomerated(d, &m.groupOf, &m.ownerOf);
121 bool ok = liftable(m.dec);
122 // Fat enough to STAY above the threshold after the halving that follows (a single block always
123 // qualifies): merging to blocks of 4 that become 2 on the next level would merge again there.
124 if (ok && minExtent > 0 && m.dec.numBlocks() > 1 &&
125 minBlockExtent(m.dec) < 2 * static_cast<Index>(minExtent))
126 ok = false;
127 if (ok && m.dec.numBlocks() < cur.numBlocks())
128 return m;
129 }
130 return std::nullopt;
131}
132
152template <int Dim, class Liftable>
153std::optional<StageTarget<Dim>> repartitionTarget(const BlockDecomposer<Dim>& cur,
154 const std::type_identity_t<IVec<Dim>>& levelGrid,
157 if (maxBlockCells <= 0)
158 throw std::invalid_argument("repartitionTarget: maxBlockCells must be positive");
159 const Index np = static_cast<Index>(cur.numBlocks());
160 Index cells = 1;
161 for (int k = 0; k < Dim; ++k)
162 cells *= levelGrid[k];
163 Index npL = (cells + maxBlockCells - 1) / maxBlockCells;
164 Index cap = np; // the extent cap; no cap beyond the rank count at minExtent == 0
165 if (minExtent > 0) {
166 Index c = 1;
167 for (int k = 0; k < Dim; ++k) {
168 const Index f = levelGrid[k] / (2 * static_cast<Index>(minExtent));
169 c *= f > 1 ? f : 1;
170 }
171 cap = c < np ? c : np;
172 npL = npL < cap ? npL : cap;
173 }
174 Index up = 1; // nextPow2(npL), stopping once it reaches np (the clamp below)
175 while (up < npL && up < np)
176 up *= 2;
177 npL = up < np ? up : np;
178 if (npL > cap) { // the round-up overshot a cap that is not a power of two: the largest power
179 Index p = 1; // of two within the cap keeps the blocks fat enough (the cap's whole point)
180 while (2 * p <= cap)
181 p *= 2;
182 npL = p;
183 }
184 npL = npL < 1 ? 1 : npL;
185 Index p2 = 1; // the largest power of two <= npL
186 while (2 * p2 <= npL)
187 p2 *= 2;
188 std::vector<Index> tries; // npL, then p2 (unless it is npL), then its halvings down to 1
189 if (npL != p2)
190 tries.push_back(npL);
191 for (Index n = p2; n >= 1; n /= 2)
192 tries.push_back(n);
193 for (const Index n : tries) {
194 BlockDecomposer<Dim> r(static_cast<std::size_t>(n), levelGrid);
195 if (liftable(r)) {
198 t.dec = std::move(r);
199 t.ownerOf.resize(static_cast<std::size_t>(n));
200 for (Index b = 0; b < n; ++b)
201 t.ownerOf[static_cast<std::size_t>(b)] = static_cast<int>(b);
202 return t;
203 }
204 }
205 return std::nullopt;
206}
207
245template <int Dim, class Liftable>
247 const std::type_identity_t<IVec<Dim>>& levelGrid,
249 if (levelGrid != cur.globalSize())
250 throw std::invalid_argument(
251 "chooseStageTarget: levelGrid differs from the current decomposition's global size");
252 if (maxBlockCells < 0)
253 throw std::invalid_argument("chooseStageTarget: maxBlockCells must be >= 0 (0 disables)");
254
255 const std::size_t nb = cur.numBlocks();
257
258 const bool tooSmall = minExtent > 0 && minBlockExtent(cur) < static_cast<Index>(minExtent);
259 if (nb <= 1 || (liftable(cur) && !tooSmall)) {
260 t.kind = StageKind::InPlace;
261 t.dec = cur;
262 t.ownerOf.resize(nb);
263 for (std::size_t b = 0; b < nb; ++b)
264 t.ownerOf[b] = static_cast<int>(b);
265 return t;
266 }
267
269 auto merge = [&] {
271 t.dec = std::move(m->dec);
272 t.ownerOf = std::move(m->ownerOf);
273 t.groupOf = std::move(m->groupOf);
274 return std::move(t);
275 };
276 if (m && (maxBlockCells == 0 || largestBlockCells(m->dec) <= maxBlockCells))
277 return merge();
278
279 if (maxBlockCells > 0)
281 return std::move(*r);
282
283 if (m)
284 return merge();
285
287 t.dec.init(1, levelGrid);
288 t.ownerOf = {0};
289 return t;
290}
291
292} // namespace peclet::core::decomp
293
294#endif // PECLET_CORE_DECOMP_STAGE_TARGET_HPP
Index largestBlockCells(const BlockDecomposer< Dim > &d)
Cell count of the largest block — replicated, like the decomposition.
StageKind
How a multigrid level moves when it can no longer coarsen in place (see chooseStageTarget and the fil...
@ Repartition
a fresh proportional ORB of the level grid on the first np_L parent ranks
@ InPlace
no stage: the level coarsens on its current decomposition
@ SiblingMerge
ORB tree truncated: contiguous runs of blocks merge onto their lowest rank.
@ Replicated
the whole level as one block, held identically on every rank
std::optional< StageTarget< Dim > > repartitionTarget(const BlockDecomposer< Dim > &cur, const std::type_identity_t< IVec< Dim > > &levelGrid, Liftable &&liftable, int minExtent, Index maxBlockCells)
The Repartition candidate (design §11.2): a fresh proportional, unweighted, unaligned ORB of the leve...
std::optional< SiblingMergeChoice< Dim > > shallowestLiftableMerge(const BlockDecomposer< Dim > &cur, Liftable &&liftable, int minExtent)
The sibling-merge SEARCH on its own, with no trigger: the shallowest merge — fewest merges,...
Index minBlockExtent(const BlockDecomposer< Dim > &d)
Smallest block extent over every block and axis — replicated, like the decomposition.
StageTarget< Dim > chooseStageTarget(const BlockDecomposer< Dim > &cur, const std::type_identity_t< IVec< Dim > > &levelGrid, Liftable &&liftable, int minExtent, Index maxBlockCells=0)
Choose the stage target of a level whose current decomposition is cur over the level grid levelGrid (...
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15
A sibling merge the search chose: the tree depth it truncates at, the merged decomposition,...
std::vector< int > groupOf
current block -> merged block
std::vector< int > ownerOf
merged block -> lowest parent rank of its group
BlockDecomposer< Dim > dec
the merged decomposition
int depth
ORB tree depth the merge truncates at (agglomerated(depth))
Where a level goes: the result of chooseStageTarget / repartitionTarget.
BlockDecomposer< Dim > dec
The target decomposition, of the SAME level grid as the current one.
std::vector< int > groupOf
Current block -> target block. SiblingMerge only (agglomerated's groupOf); empty otherwise.
std::vector< int > ownerOf
Target block -> the rank that owns it, in the PARENT communicator (whose rank r owns block r of the c...
StageKind kind
which movement reaches dec