core
Shared MPI block decomposition + asynchronous ghost-layer exchange (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// tpx namespace. The global cell grid is split recursively along its largest axis into `numBlocks`
5// 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 <cassert>
10#include <cmath>
11#include <cstddef>
12#include <limits>
13#include <stack>
14#include <vector>
15
17
19
21template <int Dim>
26
27template <int Dim>
29 public:
30 BlockDecomposer() = default;
32 BlockDecomposer(std::size_t numBlocks, IVec<Dim> globalSize, const std::vector<Real>& weights) {
34 }
35
38 void init(std::size_t numBlocks, IVec<Dim> globalSize) {
39 initImpl(numBlocks, globalSize, nullptr);
40 }
41
47 void init(std::size_t numBlocks, IVec<Dim> globalSize, const std::vector<Real>& weights) {
48 assert(weights.size() == static_cast<std::size_t>([&] {
49 Index v = 1;
50 for (int i = 0; i < Dim; ++i)
51 v *= globalSize[i];
52 return v;
53 }()) &&
54 "weights array must cover the global grid (x-fastest)");
55 initImpl(numBlocks, globalSize, &weights);
56 }
57
58 std::size_t numBlocks() const { return origins_.size(); }
59 const IVec<Dim>& globalSize() const { return globalSize_; }
60 const std::vector<IVec<Dim>>& origins() const { return origins_; }
61 const std::vector<IVec<Dim>>& sizes() const { return sizes_; }
62
63 Block<Dim> block(std::size_t b) const { return {origins_[b], sizes_[b]}; }
64
66 int ownerOf(const IVec<Dim>& g) const {
67 Index node = 0;
68 while (tree_[node].splitDim != -1) {
69 node = (g[tree_[node].splitDim] < tree_[node].splitValue) ? 2 * node + 1 : 2 * node + 2;
70 }
71 return static_cast<int>(tree_[node].splitValue);
72 }
73
79 void flattenTree(std::vector<int>& splitDim, std::vector<Index>& splitVal) const {
80 splitDim.resize(tree_.size());
81 splitVal.resize(tree_.size());
82 for (std::size_t i = 0; i < tree_.size(); ++i) {
83 splitDim[i] = tree_[i].splitDim;
84 splitVal[i] = tree_[i].splitValue;
85 }
86 }
87
89 Index linearGlobal(const IVec<Dim>& g) const {
90 Index idx = g[Dim - 1];
91 for (int i = Dim - 2; i >= 0; --i) {
92 idx *= globalSize_[i];
93 idx += g[i];
94 }
95 return idx;
96 }
97
100 IVec<Dim> g{};
101 for (int i = 0; i < Dim; ++i) {
102 g[i] = lin % globalSize_[i];
103 lin /= globalSize_[i];
104 }
105 return g;
106 }
107
108 private:
109 struct TreeNode {
110 int splitDim = -1;
111 Index splitValue = 0;
112 };
113
116 void initImpl(std::size_t numBlocks, IVec<Dim> globalSize, const std::vector<Real>* weights);
117
122 Index splitPosition(const IVec<Dim>& origin, const IVec<Dim>& size, int kLargest,
123 std::size_t numSub, std::size_t numTotal,
124 const std::vector<Real>* weights) const;
125
126 IVec<Dim> globalSize_{};
127 std::vector<IVec<Dim>> origins_;
128 std::vector<IVec<Dim>> sizes_;
129 std::vector<TreeNode> tree_;
130};
131
132template <int Dim>
133void BlockDecomposer<Dim>::initImpl(std::size_t numBlocks, IVec<Dim> globalSize,
134 const std::vector<Real>* weights) {
135 globalSize_ = globalSize;
136 origins_.clear();
137 sizes_.clear();
138 tree_.clear();
139
140 struct StackBlock {
141 IVec<Dim> origin;
142 IVec<Dim> size;
143 std::size_t numSub;
144 Index treeIndx;
145 };
146 std::stack<StackBlock> stack;
147 stack.push(StackBlock{IVec<Dim>{}, globalSize, numBlocks, 0});
148
149 Index leafIndex = 0;
150 while (!stack.empty()) {
151 StackBlock cur = stack.top();
152 stack.pop();
153
154 if (static_cast<std::size_t>(cur.treeIndx) >= tree_.size()) {
155 tree_.resize(cur.treeIndx + 1);
156 }
157
158 if (cur.numSub > 1) {
159 // Split along the largest axis. The split position balances either the sub-block cell count
160 // (unweighted) or the cumulative weight (weighted) of the two children.
161 int kLargest = 0;
162 for (int k = 1; k < Dim; ++k) {
163 if (cur.size[k] > cur.size[kLargest])
164 kLargest = k;
165 }
166 std::size_t numSub = cur.numSub / 2;
167 Index szSub = splitPosition(cur.origin, cur.size, kLargest, numSub, cur.numSub, weights);
168
170 left.numSub = numSub;
171 left.size[kLargest] = szSub;
172 left.treeIndx = 2 * cur.treeIndx + 1;
173
175 right.origin[kLargest] += szSub;
176 right.size[kLargest] -= szSub;
177 right.numSub = cur.numSub - numSub;
178 right.treeIndx = 2 * cur.treeIndx + 2;
179
180 tree_[cur.treeIndx] = TreeNode{kLargest, cur.origin[kLargest] + szSub};
181 stack.push(right);
182 stack.push(left);
183 } else {
184 tree_[cur.treeIndx] = TreeNode{-1, leafIndex++};
185 origins_.push_back(cur.origin);
186 sizes_.push_back(cur.size);
187 }
188 }
189}
190
191template <int Dim>
192Index BlockDecomposer<Dim>::splitPosition(const IVec<Dim>& origin, const IVec<Dim>& size,
193 int kLargest, std::size_t numSub, std::size_t numTotal,
194 const std::vector<Real>* weights) const {
195 const Index n = size[kLargest];
196
197 // Unweighted (and the degenerate non-splittable axis): the classic proportional-to-count split.
198 // Kept as the exact same expression so the unweighted API is byte-for-byte unchanged.
199 if (weights == nullptr || n <= 1) {
200 return static_cast<Index>(std::round(static_cast<double>(n) * static_cast<double>(numSub) /
201 static_cast<double>(numTotal)));
202 }
203
204 // Weighted: accumulate the weight of each slab (fixed kLargest-coordinate) within the box, then
205 // pick the boundary whose cumulative weight is closest to the target fraction of the total.
206 std::vector<double> slab(static_cast<std::size_t>(n), 0.0);
207 IVec<Dim> bgn = origin, end{};
208 for (int i = 0; i < Dim; ++i)
209 end[i] = origin[i] + size[i];
210 forEachInBox<Dim>(bgn, end, [&](const IVec<Dim>& g) {
211 slab[static_cast<std::size_t>(g[kLargest] - origin[kLargest])] += (*weights)[linearGlobal(g)];
212 });
213
214 double total = 0.0;
215 for (double w : slab)
216 total += w;
217 const double target = total * static_cast<double>(numSub) / static_cast<double>(numTotal);
218
219 // Search boundaries in [1, n-1] (non-empty children). Ties resolve to the larger boundary, which
220 // mirrors std::round's half-away-from-zero rule so equal weights reproduce the unweighted split.
221 double cum = 0.0;
222 double bestDist = std::numeric_limits<double>::max();
223 Index best = 1;
224 for (Index s = 1; s < n; ++s) {
225 cum += slab[static_cast<std::size_t>(s - 1)];
226 const double dist = std::abs(cum - target);
227 if (dist <= bestDist) {
228 bestDist = dist;
229 best = s;
230 }
231 }
232 return best;
233}
234
235} // namespace peclet::core::decomp
236
237#endif // PECLET_CORE_DECOMP_BLOCK_DECOMPOSER_HPP
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
const std::vector< IVec< Dim > > & sizes() const
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)
const std::vector< IVec< Dim > > & origins() const
const IVec< Dim > & globalSize() const
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.
BlockDecomposer(std::size_t numBlocks, IVec< Dim > globalSize)
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).
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 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