flow 1.3.0
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 {
60 MPI_Comm comm;
61 int rank, size;
62 std::vector<Level> lv;
63
64 void init(MPI_Comm c, IVec<kDim> gs) {
65 comm = c;
66 MPI_Comm_rank(comm, &rank);
67 MPI_Comm_size(comm, &size);
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", peclet::flow::MDRange3<CCExec>(sp, {G, G, G}, {e.x - G, e.y - G, e.z - G}),
115 KOKKOS_LAMBDA(int x, int y, int z, double& a) {
116 a += f((long)x + (long)y * e.x + (long)z * (long)e.x * e.y);
117 },
118 s);
119 double g = 0;
120 MPI_Allreduce(&s, &g, 1, MPI_DOUBLE, MPI_SUM, comm);
121 return g;
122 }
123 void removeMean(Level& v, CCField f) {
124 const double mean = meanSum(v, f) / (double)v.gCells;
125 CCExec sp;
126 C3 e = v.e;
127 CCField ff = f;
128 Kokkos::parallel_for(
129 "rm", peclet::flow::MDRange3<CCExec>(sp, {G, G, G}, {e.x - G, e.y - G, e.z - G}),
130 KOKKOS_LAMBDA(int x, int y, int z) {
131 ff((long)x + (long)y * e.x + (long)z * (long)e.x * e.y) -= mean;
132 });
133 sp.fence();
134 }
135 void smooth(Level& v, int sweeps) {
136 for (int k = 0; k < sweeps; ++k)
137 for (int color = 0; color < 2; ++color) {
138 v.dev->exchange(v.x);
139 peclet::flow::cutcellSmoothColor(v.x, CCConst(v.rhs), FPC(v.AC), FPC(v.AW), FPC(v.AE),
140 FPC(v.AS), FPC(v.AN), FPC(v.AB), FPC(v.AT), v.e, v.og, G,
141 color);
142 }
143 }
144 void vcycle(int L) {
145 Level& v = lv[L];
146 if (L + 1 == (int)lv.size()) {
147 smooth(v, BOTTOM);
148 removeMean(v, v.x);
149 return;
150 }
151 smooth(v, PRE);
152 v.dev->exchange(v.x);
153 peclet::flow::residualCutcell(v.res, CCConst(v.x), CCConst(v.rhs), FPC(v.AC), FPC(v.AW),
154 FPC(v.AE), FPC(v.AS), FPC(v.AN), FPC(v.AB), FPC(v.AT), v.e, G);
155 Level& c = lv[L + 1];
156 peclet::flow::restrictAvg(c.rhs, CCConst(v.res), c.e, v.e, G, G, c.inner,
157 v.ratio); // LOCAL (coarse block = fine/2)
158 Kokkos::deep_copy(c.x, 0.0);
159 vcycle(L + 1);
160 c.dev->exchange(c.x);
161 peclet::flow::prolongAdd(v.x, CCConst(c.x), v.e, c.e, G, G, v.inner, v.ratio); // LOCAL
162 smooth(v, POST);
163 removeMean(v, v.x);
164 }
165 // solve A x = b by NVCYC V-cycles; b set into level-0 rhs (mean removed). Solution left in
166 // level-0 x.
167 void solve(CCField b) {
168 Level& l0 = lv[0];
169 Kokkos::deep_copy(l0.rhs, b);
170 removeMean(l0, l0.rhs);
171 Kokkos::deep_copy(l0.x, 0.0);
172 for (int v = 0; v < NVCYC; ++v)
173 vcycle(0);
174 }
175};
176
177int main(int argc, char** argv) {
178 MPI_Init(&argc, &argv);
179 Kokkos::initialize(argc, argv);
180 int fail = 0, size = 1, rank = 0;
181 {
182 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
183 MPI_Comm_size(MPI_COMM_WORLD, &size);
184 IVec<kDim> gs{32, 32, 32};
185
186 // distributed V-cycle solve
187 DistMG mg;
188 mg.init(MPI_COMM_WORLD, gs);
189 Level& l0 = mg.lv[0];
190 CCField b("b", l0.n);
191 {
192 auto hb = Kokkos::create_mirror_view(b);
193 for (Index c = 0; c < l0.n; ++c)
194 hb(c) = 0.0;
195 mg.lv[0].halo->indexer().forEachInner([&](const IVec<kDim>& l) {
196 hb(mg.lv[0].halo->indexer().localMdToLocal(l)) =
197 source(l[0] + l0.og.x, l[1] + l0.og.y, l[2] + l0.og.z, gs);
198 });
199 Kokkos::deep_copy(b, hb);
200 }
201 mg.solve(b);
202 auto hx = Kokkos::create_mirror_view(l0.x);
203 Kokkos::deep_copy(hx, l0.x);
204
205 // serial reference: same code, MPI_COMM_SELF (size-1 block = full grid)
206 DistMG ref;
207 ref.init(MPI_COMM_SELF, gs);
208 Level& r0 = ref.lv[0];
209 CCField rb("rb", r0.n);
210 {
211 auto hrb = Kokkos::create_mirror_view(rb);
212 for (Index c = 0; c < r0.n; ++c)
213 hrb(c) = 0.0;
214 ref.lv[0].halo->indexer().forEachInner([&](const IVec<kDim>& l) {
215 hrb(ref.lv[0].halo->indexer().localMdToLocal(l)) =
216 source(l[0] + r0.og.x, l[1] + r0.og.y, l[2] + r0.og.z, gs);
217 });
218 Kokkos::deep_copy(rb, hrb);
219 }
220 ref.solve(rb);
221 auto hr = Kokkos::create_mirror_view(r0.x);
222 Kokkos::deep_copy(hr, r0.x);
223
224 const C3 re = r0.e;
225 double maxdiff = 0.0;
226 mg.lv[0].halo->indexer().forEachInner([&](const IVec<kDim>& l) {
227 const int gx = l[0] + l0.og.x, gy = l[1] + l0.og.y, gz = l[2] + l0.og.z;
228 const double a = hx(mg.lv[0].halo->indexer().localMdToLocal(l));
229 const double rr =
230 hr((long)(gx + G) + (long)(gy + G) * re.x + (long)(gz + G) * (long)re.x * re.y);
231 const double d = std::fabs(a - rr);
232 if (d > maxdiff)
233 maxdiff = d;
234 });
235 if (maxdiff > 1e-11) {
236 ++fail;
237 std::fprintf(stderr, "[rank %d] max|distributed - serial V-cycle| = %.3e\n", rank, maxdiff);
238 }
239 }
240 int totalFail = 0;
241 MPI_Allreduce(&fail, &totalFail, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
242 if (rank == 0) {
243 if (totalFail == 0)
244 std::printf("OK (np=%d): distributed cut-cell MG V-cycle == serial reference\n", size);
245 else
246 std::fprintf(stderr, "FAILED (np=%d): %d ranks differ\n", size, totalFail);
247 }
248 Kokkos::finalize();
249 MPI_Finalize();
250 return totalFail == 0 ? 0 : 1;
251}
int main()
Definition dbg_scene.cpp:13
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 gf, int gc, 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 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
void restrictAvg(CCField coarse, CCConst fine, C3 cext, C3 fext, int gc, int gf, C3 cinner, C3 ratio)
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
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
static double mean(const std::vector< double > &a)