flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_distributed_mg.cpp
Go to the documentation of this file.
1// cfd-gpu — distributed (MPI) cut-cell pressure MULTIGRID V-cycle.
2//
3// The third MPI milestone: the geometric V-cycle itself, decomposed over ranks. A level hierarchy
4// is built by coarsening the global grid 2:1 per level; each level gets its OWN transport-core halo
5// (GridHalo over a BlockDecomposer of that level's grid). The transport-core ORB decomposition
6// coarsens cleanly -- each rank's coarse block is exactly its fine block halved -- so the
7// restriction / prolongation stay LOCAL (a coarse cell's 2^3 fine children all live on the same
8// rank); only the smoother / residual / prolong ghosts cross ranks, via the per-level halo. The
9// singular periodic null space is removed with the distributed mean (MPI_Allreduce). Running the
10// same code with MPI_COMM_SELF (a size-1 block = the full grid) is the serial reference: the
11// distributed V-cycle must match it to ~1e-11. Validates the per-level- halo + local-transfer
12// pattern that turns the single-GPU CutcellMG into the multi-rank pressure multigrid.
13#include <mpi.h>
14
15#include <array>
16#include <cmath>
17#include <cstdio>
18#include <Kokkos_Core.hpp>
19#include <vector>
20
21#include "mac_cutcell_mg.hpp" // restrictAvg, prolongAdd, residualCutcell, buildCutcellOp, cutcellSmoothColor
22#include "peclet/core/common/types.hpp"
23#include "peclet/core/common/view.hpp"
24#include "peclet/core/decomp/block_decomposer.hpp"
25#include "peclet/core/halo/grid_halo.hpp"
26#include "peclet/core/halo/grid_halo_topology.hpp"
27
28using peclet::core::Index;
29using peclet::core::IVec;
30using peclet::core::decomp::BlockDecomposer;
31using peclet::core::halo::GridHalo;
32using peclet::core::halo::GridHaloTopology;
39
40static constexpr int kDim = 3, G = 1, NLEV = 4, NVCYC = 6, PRE = 2, POST = 2, BOTTOM = 8;
41
42static double source(int gx, int gy, int gz, IVec<kDim> gs) {
43 return std::sin(2.0 * M_PI * gx / gs[0]) +
44 std::cos(4.0 * M_PI * gy / gs[1]) * std::sin(2.0 * M_PI * gz / gs[2]);
45}
46
47struct Level {
48 std::unique_ptr<GridHaloTopology<kDim>> halo;
49 std::unique_ptr<GridHalo<double>> dev;
50 C3 e, inner, og, ratio{2, 2, 2}, cfac{1, 1, 1};
51 Index n = 0;
52 long gCells = 0;
53 CCField x, rhs, res, ox, oy, oz;
54 FPV AC, AW, AE, AS, AN, AB, AT;
55};
56
57// Distributed multigrid for the periodic all-fluid Laplacian; same code for MPI_COMM_WORLD
58// (decomposed) and MPI_COMM_SELF (size-1 full-grid serial reference).
59struct DistMG {
61 int rank, size;
62 std::vector<Level> lv;
63
65 comm = c;
68 std::array<bool, kDim> per{true, true, true};
69 IVec<kDim> g = gs, cf{1, 1, 1};
70 for (int L = 0; L < NLEV; ++L) {
71 Level v;
72 v.halo = std::make_unique<GridHaloTopology<kDim>>();
73 BlockDecomposer<kDim> dec(static_cast<std::size_t>(size), g);
74 v.halo->buildTopology(dec, rank, G, per, comm);
75 v.dev = std::make_unique<GridHalo<double>>();
76 v.dev->init(*v.halo);
77 const auto& idx = v.halo->indexer();
78 const auto eg = idx.sizeInclGhost(), ino = idx.sizeInner(), o = idx.originInclGhost();
79 v.e = {(int)eg[0], (int)eg[1], (int)eg[2]};
80 v.inner = {(int)ino[0], (int)ino[1], (int)ino[2]};
81 v.og = {(int)o[0], (int)o[1], (int)o[2]};
82 v.cfac = {(int)cf[0], (int)cf[1], (int)cf[2]};
83 v.n = idx.numCellsInclGhost();
84 v.gCells = (long)g[0] * g[1] * g[2];
85 v.x = CCField("x", v.n);
86 v.rhs = CCField("rhs", v.n);
87 v.res = CCField("res", v.n);
88 v.ox = CCField("ox", v.n);
89 v.oy = CCField("oy", v.n);
90 v.oz = CCField("oz", v.n);
91 Kokkos::deep_copy(v.ox, 1.0);
92 Kokkos::deep_copy(v.oy, 1.0);
93 Kokkos::deep_copy(v.oz, 1.0); // all-fluid
94 for (FPV* p : {&v.AC, &v.AW, &v.AE, &v.AS, &v.AN, &v.AB, &v.AT})
95 *p = FPV("A", v.n);
96 const double s = 1.0 / (double)(cf[0]); // uniform: cfac isotropic here
97 peclet::flow::buildCutcellOp(v.AC, v.AW, v.AE, v.AS, v.AN, v.AB, v.AT, CCConst(v.ox),
98 CCConst(v.oy), CCConst(v.oz), v.e, G, 1.0 / (cf[0] * cf[0]),
99 1.0 / (cf[1] * cf[1]), 1.0 / (cf[2] * cf[2]));
100 (void)s;
101 lv.push_back(std::move(v));
102 for (int d = 0; d < 3; ++d) {
103 g[d] /= 2;
104 cf[d] *= 2;
105 }
106 }
107 }
108
109 double meanSum(Level& v, CCField f) {
110 CCExec sp;
111 double s = 0;
112 C3 e = v.e;
113 Kokkos::parallel_reduce(
114 "ms",
115 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>(sp, {G, G, G}, {e.x - G, e.y - G, e.z - G}),
116 KOKKOS_LAMBDA(int x, int y, int z, double& a) {
117 a += f((long)x + (long)y * e.x + (long)z * (long)e.x * e.y);
118 },
119 s);
120 double g = 0;
122 return g;
123 }
124 void removeMean(Level& v, CCField f) {
125 const double mean = meanSum(v, f) / (double)v.gCells;
126 CCExec sp;
127 C3 e = v.e;
128 CCField ff = f;
129 Kokkos::parallel_for(
130 "rm",
131 Kokkos::MDRangePolicy<CCExec, Kokkos::Rank<3>>(sp, {G, G, G}, {e.x - G, e.y - G, e.z - G}),
132 KOKKOS_LAMBDA(int x, int y, int z) {
133 ff((long)x + (long)y * e.x + (long)z * (long)e.x * e.y) -= mean;
134 });
135 sp.fence();
136 }
137 void smooth(Level& v, int sweeps) {
138 for (int k = 0; k < sweeps; ++k)
139 for (int color = 0; color < 2; ++color) {
140 v.dev->exchange(v.x);
141 peclet::flow::cutcellSmoothColor(v.x, CCConst(v.rhs), FPC(v.AC), FPC(v.AW), FPC(v.AE),
142 FPC(v.AS), FPC(v.AN), FPC(v.AB), FPC(v.AT), v.e, v.og, G,
143 color);
144 }
145 }
146 void vcycle(int L) {
147 Level& v = lv[L];
148 if (L + 1 == (int)lv.size()) {
149 smooth(v, BOTTOM);
150 removeMean(v, v.x);
151 return;
152 }
153 smooth(v, PRE);
154 v.dev->exchange(v.x);
155 peclet::flow::residualCutcell(v.res, CCConst(v.x), CCConst(v.rhs), FPC(v.AC), FPC(v.AW),
156 FPC(v.AE), FPC(v.AS), FPC(v.AN), FPC(v.AB), FPC(v.AT), v.e, G);
157 Level& c = lv[L + 1];
158 peclet::flow::restrictAvg(c.rhs, CCConst(v.res), c.e, v.e, G, c.inner,
159 v.ratio); // LOCAL (coarse block = fine/2)
160 Kokkos::deep_copy(c.x, 0.0);
161 vcycle(L + 1);
162 c.dev->exchange(c.x);
163 peclet::flow::prolongAdd(v.x, CCConst(c.x), v.e, c.e, G, v.inner, v.ratio); // LOCAL
164 smooth(v, POST);
165 removeMean(v, v.x);
166 }
167 // solve A x = b by NVCYC V-cycles; b set into level-0 rhs (mean removed). Solution left in
168 // level-0 x.
169 void solve(CCField b) {
170 Level& l0 = lv[0];
171 Kokkos::deep_copy(l0.rhs, b);
172 removeMean(l0, l0.rhs);
173 Kokkos::deep_copy(l0.x, 0.0);
174 for (int v = 0; v < NVCYC; ++v)
175 vcycle(0);
176 }
177};
178
179int main(int argc, char** argv) {
180 MPI_Init(&argc, &argv);
181 Kokkos::initialize(argc, argv);
182 int fail = 0, size = 1, rank = 0;
183 {
184 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
185 MPI_Comm_size(MPI_COMM_WORLD, &size);
186 IVec<kDim> gs{32, 32, 32};
187
188 // distributed V-cycle solve
189 DistMG mg;
190 mg.init(MPI_COMM_WORLD, gs);
191 Level& l0 = mg.lv[0];
192 CCField b("b", l0.n);
193 {
194 auto hb = Kokkos::create_mirror_view(b);
195 for (Index c = 0; c < l0.n; ++c)
196 hb(c) = 0.0;
197 mg.lv[0].halo->indexer().forEachInner([&](const IVec<kDim>& l) {
198 hb(mg.lv[0].halo->indexer().localMdToLocal(l)) =
199 source(l[0] + l0.og.x, l[1] + l0.og.y, l[2] + l0.og.z, gs);
200 });
201 Kokkos::deep_copy(b, hb);
202 }
203 mg.solve(b);
204 auto hx = Kokkos::create_mirror_view(l0.x);
205 Kokkos::deep_copy(hx, l0.x);
206
207 // serial reference: same code, MPI_COMM_SELF (size-1 block = full grid)
208 DistMG ref;
209 ref.init(MPI_COMM_SELF, gs);
210 Level& r0 = ref.lv[0];
211 CCField rb("rb", r0.n);
212 {
213 auto hrb = Kokkos::create_mirror_view(rb);
214 for (Index c = 0; c < r0.n; ++c)
215 hrb(c) = 0.0;
216 ref.lv[0].halo->indexer().forEachInner([&](const IVec<kDim>& l) {
217 hrb(ref.lv[0].halo->indexer().localMdToLocal(l)) =
218 source(l[0] + r0.og.x, l[1] + r0.og.y, l[2] + r0.og.z, gs);
219 });
220 Kokkos::deep_copy(rb, hrb);
221 }
222 ref.solve(rb);
223 auto hr = Kokkos::create_mirror_view(r0.x);
224 Kokkos::deep_copy(hr, r0.x);
225
226 const C3 re = r0.e;
227 double maxdiff = 0.0;
228 mg.lv[0].halo->indexer().forEachInner([&](const IVec<kDim>& l) {
229 const int gx = l[0] + l0.og.x, gy = l[1] + l0.og.y, gz = l[2] + l0.og.z;
230 const double a = hx(mg.lv[0].halo->indexer().localMdToLocal(l));
231 const double rr =
232 hr((long)(gx + G) + (long)(gy + G) * re.x + (long)(gz + G) * (long)re.x * re.y);
233 const double d = std::fabs(a - rr);
234 if (d > maxdiff)
235 maxdiff = d;
236 });
237 if (maxdiff > 1e-11) {
238 ++fail;
239 std::fprintf(stderr, "[rank %d] max|distributed - serial V-cycle| = %.3e\n", rank, maxdiff);
240 }
241 }
242 int totalFail = 0;
243 MPI_Allreduce(&fail, &totalFail, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
244 if (rank == 0) {
245 if (totalFail == 0)
246 std::printf("OK (np=%d): distributed cut-cell MG V-cycle == serial reference\n", size);
247 else
248 std::fprintf(stderr, "FAILED (np=%d): %d ranks differ\n", size, totalFail);
249 }
250 Kokkos::finalize();
251 MPI_Finalize();
252 return totalFail == 0 ? 0 : 1;
253}
flow — portable (Kokkos) geometric multigrid for the cut-cell (variable-openness) pressure Poisson.
Kokkos::View< const MReal *, CCMem > FPC
void prolongAdd(CCField fine, CCConst coarse, C3 fext, C3 cext, int g, C3 finner, C3 ratio)
void residualCutcell(CCField r, CCConst x, CCConst b, FPC AC, FPC AW, FPC AE, FPC AS, FPC AN, FPC AB, FPC AT, C3 e, int g)
void restrictAvg(CCField coarse, CCConst fine, C3 cext, C3 fext, int g, C3 cinner, C3 ratio)
void buildCutcellOp(OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, CCConst ox, CCConst oy, CCConst oz, C3 e, int g, double gfx, double gfy, double gfz)
Kokkos::View< double *, CCMem > CCField
Kokkos::View< MReal *, CCMem > FPV
void cutcellSmoothColor(CCField phi, CCConst b, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e, C3 og, int g, int color)
Kokkos::DefaultExecutionSpace CCExec
Kokkos::View< const double *, CCMem > CCConst
double meanSum(Level &v, CCField f)
void removeMean(Level &v, CCField f)
void vcycle(int L)
void init(MPI_Comm c, IVec< kDim > gs)
std::vector< Level > lv
void solve(CCField b)
void smooth(Level &v, int sweeps)
std::unique_ptr< GridHalo< double > > dev
std::unique_ptr< GridHaloTopology< kDim > > halo
static constexpr int kDim
static constexpr int NVCYC
int main(int argc, char **argv)
static constexpr int POST
static constexpr int PRE
static constexpr int BOTTOM
static constexpr int G
static double source(int gx, int gy, int gz, IVec< kDim > gs)
static constexpr int NLEV