core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
grid_redistribute.hpp
Go to the documentation of this file.
1// core — redistribute structured grid fields between two ORB decompositions (load balancing).
2//
3// The Lagrangian half of dynamic load balancing already exists (weighted ORB + ParticleMigrator +
4// rebalanceByParticleCount). This is the Eulerian counterpart: after a re-decomposition (e.g. a
5// weighted ORB that moved the block boundaries), each rank's grid fields must move from the OLD
6// block layout to the NEW one so a method code (flow) keeps a consistent partition shared with the
7// particles. Pure data movement of the INNER cells — bit-exact by construction (no averaging); the
8// caller refills ghosts with a halo exchange afterwards.
9//
10// Each field is a flat x-fastest buffer on the rank's LOCAL padded block: inner extent
11// dec.block(rank).size, ghost width g, so inner cell (lx,ly,lz) sits at
12// (lx+g) + (ly+g)*ex + (lz+g)*ex*ey with ex = size.x + 2g. `oldFields` live on the old block;
13// `newFields` are caller-allocated on the NEW block's padded size. Redistributes N fields together
14// (one message per rank pair carries every field's sub-box, laid out field-major). Host
15// implementation (plain C++ + the NBX engine); a device-staged variant can wrap it later like
16// GridHalo.
17#ifndef PECLET_CORE_DECOMP_GRID_REDISTRIBUTE_HPP
18#define PECLET_CORE_DECOMP_GRID_REDISTRIBUTE_HPP
19
20#include <cstring>
21#include <vector>
22
27
28namespace peclet::core::decomp {
29
30namespace detail {
31// Intersection of two global cell boxes (origin,size); returns false if empty on any axis.
32template <int Dim>
33inline bool intersectBox(const Block<Dim>& a, const Block<Dim>& b, IVec<Dim>& origin,
34 IVec<Dim>& size) {
35 for (int d = 0; d < Dim; ++d) {
36 const Index lo = a.origin[d] > b.origin[d] ? a.origin[d] : b.origin[d];
37 const Index ahi = a.origin[d] + a.size[d], bhi = b.origin[d] + b.size[d];
38 const Index hi = ahi < bhi ? ahi : bhi;
39 if (hi <= lo)
40 return false;
41 origin[d] = lo;
42 size[d] = hi - lo;
43 }
44 return true;
45}
46
47// Flat x-fastest index of a global cell within a padded local block (ghost width g).
48template <int Dim>
49inline Index localFlat(const IVec<Dim>& gcell, const Block<Dim>& blk, int g) {
50 Index stride = 1, idx = 0;
51 for (int d = 0; d < Dim; ++d) {
52 idx += (gcell[d] - blk.origin[d] + g) * stride;
53 stride *= (blk.size[d] + 2 * g);
54 }
55 return idx;
56}
57
58inline Index boxCellCount(const IVec<3>& size) {
59 return size[0] * size[1] * size[2];
60}
61
62// Visit every cell of a global box in x-fastest order: f(n, gcell) with n the box-linear index.
63template <class F>
64inline void forBox(const IVec<3>& origin, const IVec<3>& size, F&& f) {
65 Index n = 0;
66 for (Index z = 0; z < size[2]; ++z)
67 for (Index y = 0; y < size[1]; ++y)
68 for (Index x = 0; x < size[0]; ++x, ++n)
69 f(n, IVec<3>{origin[0] + x, origin[1] + y, origin[2] + z});
70}
71} // namespace detail
72
82template <class T>
84 int rank, int g, const std::vector<const T*>& oldFields,
85 const std::vector<T*>& newFields, MPI_Comm comm) {
86 const int nF = static_cast<int>(oldFields.size());
87 // A rank with no block in `newDec` (fewer new blocks than ranks) only sends: nothing is addressed
88 // to it, so `nbSelf` is never read there.
89 const Block<3> ob = oldDec.block(rank),
90 nbSelf = static_cast<std::size_t>(rank) < newDec.numBlocks() ? newDec.block(rank)
91 : Block<3>{};
92
93 struct Task {
94 int dst;
95 IVec<3> origin, size;
96 };
97 std::vector<Task> sends;
98 for (std::size_t d = 0; d < newDec.numBlocks(); ++d) {
99 IVec<3> o, s;
100 if (detail::intersectBox<3>(ob, newDec.block(d), o, s))
101 sends.push_back({static_cast<int>(d), o, s});
102 }
103
104 // Self contribution: old local -> new local, directly.
105 for (const auto& t : sends)
106 if (t.dst == rank)
107 detail::forBox(t.origin, t.size, [&](Index, const IVec<3>& c) {
108 const Index si = detail::localFlat<3>(c, ob, g), di = detail::localFlat<3>(c, nbSelf, g);
109 for (int f = 0; f < nF; ++f)
110 newFields[f][di] = oldFields[f][si];
111 });
112
113 // Cross-rank: message = [origin(3), size(3)] then field-major box values (fp[f*cells + n]).
114 halo::NbxEngine nbx(comm);
115 std::size_t si = 0;
116 nbx.exchange(
117 [&](std::vector<char>& out) -> int {
118 while (si < sends.size() && sends[si].dst == rank)
119 ++si;
120 if (si >= sends.size())
121 return -1;
122 const Task t = sends[si++];
123 const Index cells = detail::boxCellCount(t.size);
124 out.resize(sizeof(Index) * 6 + sizeof(T) * cells * nF);
125 char* p = out.data();
126 std::memcpy(p, t.origin.data(), sizeof(Index) * 3);
127 std::memcpy(p + sizeof(Index) * 3, t.size.data(), sizeof(Index) * 3);
128 T* fp = reinterpret_cast<T*>(p + sizeof(Index) * 6);
129 detail::forBox(t.origin, t.size, [&](Index n, const IVec<3>& c) {
130 const Index sidx = detail::localFlat<3>(c, ob, g);
131 for (int f = 0; f < nF; ++f)
132 fp[f * cells + n] = oldFields[f][sidx];
133 });
134 return t.dst;
135 },
136 [&](int, std::vector<char>& msg) {
137 const char* p = msg.data();
138 IVec<3> o, s;
139 std::memcpy(o.data(), p, sizeof(Index) * 3);
140 std::memcpy(s.data(), p + sizeof(Index) * 3, sizeof(Index) * 3);
141 const Index cells = detail::boxCellCount(s);
142 const T* fp = reinterpret_cast<const T*>(p + sizeof(Index) * 6);
143 detail::forBox(o, s, [&](Index n, const IVec<3>& c) {
144 const Index di = detail::localFlat<3>(c, nbSelf, g);
145 for (int f = 0; f < nF; ++f)
146 newFields[f][di] = fp[f * cells + n];
147 });
148 });
149}
150
151} // namespace peclet::core::decomp
152
153#endif // PECLET_CORE_DECOMP_GRID_REDISTRIBUTE_HPP
int MPI_Comm
Definition mpi_stub.hpp:17
Index boxCellCount(const IVec< 3 > &size)
Index localFlat(const IVec< Dim > &gcell, const Block< Dim > &blk, int g)
bool intersectBox(const Block< Dim > &a, const Block< Dim > &b, IVec< Dim > &origin, IVec< Dim > &size)
void forBox(const IVec< 3 > &origin, const IVec< 3 > &size, F &&f)
void redistributeGridFields(const BlockDecomposer< 3 > &oldDec, const BlockDecomposer< 3 > &newDec, int rank, int g, const std::vector< const T * > &oldFields, const std::vector< T * > &newFields, MPI_Comm comm)
Redistribute nFields grid fields from oldDec to newDec (this rank = rank).
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
IVec< Dim > size
extent in cells along each axis
IVec< Dim > origin
inclusive lower corner in global cell coordinates