core
Shared MPI block decomposition + asynchronous ghost-layer exchange (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) { return size[0] * size[1] * size[2]; }
59
60// Visit every cell of a global box in x-fastest order: f(n, gcell) with n the box-linear index.
61template <class F>
62inline void forBox(const IVec<3>& origin, const IVec<3>& size, F&& f) {
63 Index n = 0;
64 for (Index z = 0; z < size[2]; ++z)
65 for (Index y = 0; y < size[1]; ++y)
66 for (Index x = 0; x < size[0]; ++x, ++n)
67 f(n, IVec<3>{origin[0] + x, origin[1] + y, origin[2] + z});
68}
69} // namespace detail
70
71// Redistribute nFields grid fields from oldDec to newDec (this rank = `rank`). oldFields[f] is the
72// old padded local buffer, newFields[f] the (caller-allocated) new padded local buffer. Dim==3.
73template <class T>
75 int rank, int g, const std::vector<const T*>& oldFields,
76 const std::vector<T*>& newFields, MPI_Comm comm) {
77 const int nF = static_cast<int>(oldFields.size());
78 const Block<3> ob = oldDec.block(rank), nbSelf = newDec.block(rank);
79
80 struct Task {
81 int dst;
82 IVec<3> origin, size;
83 };
84 std::vector<Task> sends;
85 for (std::size_t d = 0; d < newDec.numBlocks(); ++d) {
86 IVec<3> o, s;
87 if (detail::intersectBox<3>(ob, newDec.block(d), o, s))
88 sends.push_back({static_cast<int>(d), o, s});
89 }
90
91 // Self contribution: old local -> new local, directly.
92 for (const auto& t : sends)
93 if (t.dst == rank)
94 detail::forBox(t.origin, t.size, [&](Index, const IVec<3>& c) {
95 const Index si = detail::localFlat<3>(c, ob, g), di = detail::localFlat<3>(c, nbSelf, g);
96 for (int f = 0; f < nF; ++f)
97 newFields[f][di] = oldFields[f][si];
98 });
99
100 // Cross-rank: message = [origin(3), size(3)] then field-major box values (fp[f*cells + n]).
101 halo::NbxEngine nbx(comm);
102 std::size_t si = 0;
103 nbx.exchange(
104 [&](std::vector<char>& out) -> int {
105 while (si < sends.size() && sends[si].dst == rank)
106 ++si;
107 if (si >= sends.size())
108 return -1;
109 const Task t = sends[si++];
110 const Index cells = detail::boxCellCount(t.size);
111 out.resize(sizeof(Index) * 6 + sizeof(T) * cells * nF);
112 char* p = out.data();
113 std::memcpy(p, t.origin.data(), sizeof(Index) * 3);
114 std::memcpy(p + sizeof(Index) * 3, t.size.data(), sizeof(Index) * 3);
115 T* fp = reinterpret_cast<T*>(p + sizeof(Index) * 6);
116 detail::forBox(t.origin, t.size, [&](Index n, const IVec<3>& c) {
117 const Index sidx = detail::localFlat<3>(c, ob, g);
118 for (int f = 0; f < nF; ++f)
119 fp[f * cells + n] = oldFields[f][sidx];
120 });
121 return t.dst;
122 },
123 [&](int, std::vector<char>& msg) {
124 const char* p = msg.data();
125 IVec<3> o, s;
126 std::memcpy(o.data(), p, sizeof(Index) * 3);
127 std::memcpy(s.data(), p + sizeof(Index) * 3, sizeof(Index) * 3);
129 const T* fp = reinterpret_cast<const T*>(p + sizeof(Index) * 6);
130 detail::forBox(o, s, [&](Index n, const IVec<3>& c) {
131 const Index di = detail::localFlat<3>(c, nbSelf, g);
132 for (int f = 0; f < nF; ++f)
133 newFields[f][di] = fp[f * cells + n];
134 });
135 });
136}
137
138} // namespace peclet::core::decomp
139
140#endif // PECLET_CORE_DECOMP_GRID_REDISTRIBUTE_HPP
int MPI_Comm
Definition mpi_stub.hpp:16
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)
std::array< Index, Dim > IVec
Multi-dimensional integer index / coordinate (x-fastest order by convention).
Definition types.hpp:22
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