flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_telescope_mpi.cpp
Go to the documentation of this file.
1// flow — coarse-level TELESCOPING of the production CutcellMG (docs/MG_TELESCOPING_PLAN.md).
2//
3// Three gates, all against the validated single-rank CutcellMG on the full grid (every rank runs
4// the reference redundantly and compares its own block, the test_cutcellmg_mpi pattern):
5//
6// A telescoping OFF is the baseline this suite already validates (a control run here);
7// B telescoping FORCED at level 1 on a grid where in-place coarsening is ALSO legal — the two
8// hierarchies solve the same problem to the same tolerance; the answer moves only at the
9// reduction-order floor (removeMean sums on a different partition), like coarse-first vs
10// aligned. The forced path exercises gather / sub-communicator recursion / scatter-add on a
11// hierarchy whose correctness the in-place path already vouches for;
12// C a STARVED partition — 24^3 on np ranks under a PLAIN, unaligned ORB (handed to initMpi as
13// dec0; the solver's own factory would build a nesting partition at these rank counts, which
14// is exactly what it is for). The blocks turn odd (3 wide) at global 6^3 while the grid could
15// still halve, so the in-place hierarchy freezes those axes and its coarsest grid is NOT the
16// single-rank 3^3. The gate is the design goal itself: with telescoping, at
17// least one telescope happens and the coarsest GLOBAL grid equals the single-rank solver's
18// (the hierarchy is a property of the problem, not of the rank count); and the iteration count
19// matches the single-rank one to +-1 (reduction-order steering of the adaptive PCG). The
20// no-telescope count is printed for the record, not gated.
21//
22// D a WEIGHTED level-0 decomposition (what rebalanceByWeights builds) whose ORB root split is
23// ODD: the sibling-merge search alone collapses level 0 onto ONE rank (SCALING_ISSUES #2).
24// With setRepartition(true) — what the Solver sets for a weighted dec0 — the stage is a
25// Repartition onto a proportional ORB on np_L ranks instead (amr/docs/amr_mg_core_boundary.md
26// §11.1). Gates: the precondition holds (odd root split), a Repartition stage happened, the
27// solution matches the single-rank reference and the collapsed hierarchy as the other cases
28// do, the coarsest global grid equals the single-rank one, and the iteration count is the
29// single-rank one +-1.
30//
31// np=1 has nothing to telescope (one block) and is byte-identical to the single-rank path by
32// construction; the gates run at np=2 and 4.
33#include <mpi.h>
34
35#include <cmath>
36#include <cstdio>
37#include <Kokkos_Core.hpp>
38#include <vector>
39
40#include "mac_cutcell_mg.hpp"
41#include "peclet/core/common/types.hpp"
42#include "peclet/core/decomp/block_decomposer.hpp"
43
44using peclet::core::Index;
45using peclet::core::IVec;
50
51static constexpr int G = 1;
52
53static double source(int gx, int gy, int gz, IVec<3> gs) {
54 return std::sin(2.0 * M_PI * gx / gs[0]) +
55 std::cos(4.0 * M_PI * gy / gs[1]) * std::sin(2.0 * M_PI * gz / gs[2]);
56}
57static void fillSource(CCField b, C3 ext, C3 og, IVec<3> gs) {
58 auto h = Kokkos::create_mirror_view(b);
59 for (int z = 0; z < ext.z; ++z)
60 for (int y = 0; y < ext.y; ++y)
61 for (int x = 0; x < ext.x; ++x) {
62 const long i = (long)x + (long)y * ext.x + (long)z * (long)ext.x * ext.y;
63 const bool inner =
64 x >= G && x < ext.x - G && y >= G && y < ext.y - G && z >= G && z < ext.z - G;
65 h(i) = inner ? source(x - G + og.x, y - G + og.y, z - G + og.z, gs) : 0.0;
66 }
67 Kokkos::deep_copy(b, h);
68}
69
70struct Result {
71 Kokkos::View<double*, Kokkos::HostSpace> x;
72 int iters = 0;
73};
74static Result setupAndSolve(CutcellMG& mg, C3 ext, C3 og, IVec<3> gs) {
75 const std::size_t n = (std::size_t)ext.x * ext.y * ext.z;
76 CCField ox("ox", n), oy("oy", n), oz("oz", n); // all-fluid periodic Laplacian
77 Kokkos::deep_copy(ox, 1.0);
78 Kokkos::deep_copy(oy, 1.0);
79 Kokkos::deep_copy(oz, 1.0);
80 mg.setOpenness(CCConst(ox), CCConst(oy), CCConst(oz), 1.0, 1.0, 1.0);
81 CCField b("b", n), x("x", n), r("r", n), p("p", n), z("z", n), Ap("Ap", n);
82 fillSource(b, ext, og, gs);
83 Result res;
84 res.iters = mg.solvePCG(b, x, r, p, z, Ap, /*maxit=*/300, /*rtol=*/1e-10, 2, 2, 8);
85 res.x = Kokkos::create_mirror_view(x);
86 Kokkos::deep_copy(res.x, x);
87 return res;
88}
89
90// max |distributed block - single-rank reference| over this rank's inner cells
91static double compareBlock(const Result& d, const CutcellMG::Level& l0, const Result& ref, C3 re) {
92 double maxdiff = 0.0;
93 for (int z = G; z < l0.ext.z - G; ++z)
94 for (int y = G; y < l0.ext.y - G; ++y)
95 for (int x = G; x < l0.ext.x - G; ++x) {
96 const int gx = x - G + l0.og.x, gy = y - G + l0.og.y, gz = z - G + l0.og.z;
97 const double a = d.x((long)x + (long)y * l0.ext.x + (long)z * (long)l0.ext.x * l0.ext.y);
98 const double rr =
99 ref.x((long)(gx + G) + (long)(gy + G) * re.x + (long)(gz + G) * (long)re.x * re.y);
100 maxdiff = std::max(maxdiff, std::fabs(a - rr));
101 }
102 return maxdiff;
103}
104
105int main(int argc, char** argv) {
106 MPI_Init(&argc, &argv);
107 Kokkos::initialize(argc, argv);
108 int rank = 0, size = 1;
109 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
110 MPI_Comm_size(MPI_COMM_WORLD, &size);
111 int fail = 0;
112 {
113 struct Case {
114 const char* name;
115 IVec<3> gs;
116 int nlev;
117 int force; // force a telescope at this level (-1 = only where blocked)
118 bool tele; // telescope on
119 int bottom; // agglomeration mode (0 smoother, -1 auto)
120 };
121 // B: 32^3 at np=2/4 coarsens in place to the bottom; forcing a telescope at level 1 makes
122 // a hierarchy that differs ONLY in where its coarse levels live.
123 // C: 48^3 = 2^4*3 halves four times single-rank (48->24->12->6->3); split over np ranks the
124 // blocks turn odd one or two halvings earlier and the in-place hierarchy stops there.
125 // Smoothed bottom on both so the bottom quality is what telescoping changes.
126 const Case cases[] = {
127 {"A control 32^3 in-place", {32, 32, 32}, 4, -1, false, -1},
128 {"B forced telescope@1 32^3", {32, 32, 32}, 4, 1, true, -1},
129 {"C0 starved 24^3, no telescope", {24, 24, 24}, 6, -1, false, 0},
130 {"C1 starved 24^3, telescope", {24, 24, 24}, 6, -1, true, 0},
131 };
132 int itersC0 = -1, itersC1 = -1, itersRef = -1, teleC1 = 0;
133 C3 cgC0{}, cgC1{}, cgRef{};
134 for (const Case& c : cases) {
135 if (size == 1 && c.tele)
136 continue; // nothing to telescope on one rank
137 CutcellMG mg;
138 mg.setAgglomerationMode(c.bottom);
139 mg.setTelescope(c.tele);
140 mg.setTelescopeForceLevel(c.force);
141 peclet::core::decomp::BlockDecomposer<3> plain((std::size_t)size, c.gs); // unaligned ORB
142 mg.initMpi(c.gs[0], c.gs[1], c.gs[2], c.nlev, MPI_COMM_WORLD,
143 c.name[0] == 'C' ? &plain : nullptr);
144 CutcellMG::Level& l0 = mg.level(0);
145 Result d = setupAndSolve(mg, l0.ext, l0.og, c.gs);
146
147 CutcellMG ref;
148 ref.setAgglomerationMode(c.bottom);
149 ref.init(c.gs[0], c.gs[1], c.gs[2], c.nlev);
150 const C3 re{c.gs[0] + 2 * G, c.gs[1] + 2 * G, c.gs[2] + 2 * G}, ro{0, 0, 0};
151 Result r = setupAndSolve(ref, re, ro, c.gs);
152
153 double maxdiff = compareBlock(d, l0, r, re), gmax = 0.0;
154 MPI_Allreduce(&maxdiff, &gmax, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
155 // reduction-order floor for MG-PCG with the float-stored operator: ~1e-9 (see
156 // test_cutcellmg_mpi); np=1 bit-exact.
157 const double tol = (size == 1) ? 1e-13 : 1e-7;
158 const bool ok = gmax <= tol;
159 if (!ok)
160 ++fail;
161 if (rank == 0)
162 std::printf(" %-42s np=%d levels(rank0)=%d iters=%3d max|dist-ref|=%.3e %s\n", c.name,
163 size, mg.localLevels(), d.iters, gmax, ok ? "ok" : "FAIL");
164 if (c.name[0] == 'C' && c.name[1] == '0') {
165 itersC0 = d.iters;
166 cgC0 = mg.coarsestGlobal();
167 }
168 if (c.name[0] == 'C' && c.name[1] == '1') {
169 itersC1 = d.iters;
170 itersRef = r.iters;
171 teleC1 = mg.telescopeCount();
172 cgC1 = mg.coarsestGlobal();
173 cgRef = ref.coarsestGlobal();
174 }
175 }
176 if (size > 1 && itersC1 >= 0) {
177 // rank 0 is always a group root, so it holds every level: its coarsestGlobal() is the
178 // hierarchy's. Depth-equivalence with the single-rank solver + a telescope actually occurred.
179 int tele0 = teleC1;
180 MPI_Bcast(&tele0, 1, MPI_INT, 0, MPI_COMM_WORLD);
181 int cg[3] = {cgC1.x, cgC1.y, cgC1.z};
182 MPI_Bcast(cg, 3, MPI_INT, 0, MPI_COMM_WORLD);
183 const bool sameBottom = (cg[0] == cgRef.x && cg[1] == cgRef.y && cg[2] == cgRef.z);
184 const bool ok = (tele0 >= 1) && sameBottom && (std::abs(itersC1 - itersRef) <= 1);
185 if (!ok)
186 ++fail;
187 if (rank == 0)
188 std::printf(
189 " starved 24^3: coarsest global no-telescope %dx%dx%d, telescope %dx%dx%d "
190 "(%d telescope%s), single-rank %dx%dx%d; iterations %d / %d / %d %s\n",
191 cgC0.x, cgC0.y, cgC0.z, cg[0], cg[1], cg[2], tele0, tele0 == 1 ? "" : "s", cgRef.x,
192 cgRef.y, cgRef.z, itersC0, itersC1, itersRef,
193 ok ? "ok" : "FAIL (telescoped hierarchy != single-rank)");
194 }
195 }
196 if (size > 1) {
197 // D: 48x32x32 (x the longest axis, so the ORB root splits x) with a weight 20x heavier on the
198 // slab x < 3: the root split lands on x = 3 at np = 2 and 4 (closest cumulative weight to the
199 // half), so level 0 is odd on x and the telescope fires at L0.
200 const IVec<3> gs{48, 32, 32};
201 std::vector<double> w((std::size_t)gs[0] * gs[1] * gs[2]);
202 for (Index z = 0; z < gs[2]; ++z)
203 for (Index y = 0; y < gs[1]; ++y)
204 for (Index x = 0; x < gs[0]; ++x)
205 w[(std::size_t)(x + y * gs[0] + z * gs[0] * gs[1])] = x < 3 ? 20.0 : 1.0;
206 peclet::core::decomp::BlockDecomposer<3> weighted((std::size_t)size, gs, w);
207 const bool oddRoot = (weighted.block(0).size[0] % 2) != 0;
208 const int nlev = 4;
209 Result d[2];
210 int teleN[2] = {0, 0}, repN[2] = {0, 0};
211 C3 cg[2]{};
212 for (int k = 0; k < 2; ++k) { // k = 0: sibling merges only (the collapse); 1: Repartition
213 CutcellMG mg;
215 mg.setRepartition(k == 1);
216 mg.initMpi(gs[0], gs[1], gs[2], nlev, MPI_COMM_WORLD, &weighted);
217 CutcellMG::Level& l0 = mg.level(0);
218 d[k] = setupAndSolve(mg, l0.ext, l0.og, gs);
219 teleN[k] = mg.telescopeCount();
220 repN[k] = mg.repartitionCount();
221 cg[k] = mg.coarsestGlobal();
222 if (k == 1) {
223 CutcellMG ref;
224 ref.setAgglomerationMode(-1);
225 ref.init(gs[0], gs[1], gs[2], nlev);
226 const C3 re{gs[0] + 2 * G, gs[1] + 2 * G, gs[2] + 2 * G}, ro{0, 0, 0};
227 Result r = setupAndSolve(ref, re, ro, gs);
228 double dr = compareBlock(d[1], l0, r, re), dc = 0.0;
229 for (std::size_t i = 0; i < d[1].x.extent(0); ++i)
230 dc = std::max(dc, std::fabs(d[1].x(i) - d[0].x(i)));
231 double g[2] = {dr, dc}, gmax[2] = {0.0, 0.0};
232 MPI_Allreduce(g, gmax, 2, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
233 int rep0 = repN[1], tele0 = teleN[0];
234 int cgr[3] = {cg[1].x, cg[1].y, cg[1].z};
235 MPI_Bcast(&rep0, 1, MPI_INT, 0, MPI_COMM_WORLD); // rank 0 always holds every level
236 MPI_Bcast(&tele0, 1, MPI_INT, 0, MPI_COMM_WORLD);
237 MPI_Bcast(cgr, 3, MPI_INT, 0, MPI_COMM_WORLD);
238 const C3 cgRef = ref.coarsestGlobal();
239 const bool ok = oddRoot && rep0 >= 1 && gmax[0] <= 1e-7 && gmax[1] <= 1e-7 &&
240 cgr[0] == cgRef.x && cgr[1] == cgRef.y && cgr[2] == cgRef.z &&
241 std::abs(d[1].iters - r.iters) <= 1;
242 if (!ok)
243 ++fail;
244 if (rank == 0)
245 std::printf(
246 " %-42s np=%d root split x=%ld (%s) collapse: %d telescope(s), %d iters; "
247 "repartition: %d repartition stage(s), %d iters, coarsest %dx%dx%d; single-rank "
248 "%d iters, coarsest %dx%dx%d max|rep-ref|=%.3e max|rep-collapse|=%.3e %s\n",
249 "D weighted dec0 48x32x32, repartition", size, (long)weighted.block(0).size[0],
250 oddRoot ? "odd" : "EVEN - precondition broken", tele0, d[0].iters, rep0, d[1].iters,
251 cgr[0], cgr[1], cgr[2], r.iters, cgRef.x, cgRef.y, cgRef.z, gmax[0], gmax[1],
252 ok ? "ok" : "FAIL");
253 }
254 }
255 }
256 int totalFail = 0;
257 MPI_Allreduce(&fail, &totalFail, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
258 if (rank == 0) {
259 if (totalFail == 0)
260 std::printf("OK (np=%d): telescoped CutcellMG == single-rank reference\n", size);
261 else
262 std::fprintf(stderr, "FAILED (np=%d)\n", size);
263 }
264 Kokkos::finalize();
265 MPI_Finalize();
266 return totalFail == 0 ? 0 : 1;
267}
int solvePCG(CCField b, CCField x, CCField r, CCField p, CCField z, CCField Ap, int maxit, double rtol, int pre, int post, int bottom, const StarOverlay *star=nullptr, int nStar=0, C3 nnStar=C3{0, 0, 0})
void setOpenness(CCConst ox, CCConst oy, CCConst oz, double idx2, double idy2, double idz2)
void setRepartition(bool on)
Repartition stages for a weighted level-0 decomposition (see repartition_); before initMpi.
void setAgglomerationMode(int mode)
int main()
Definition dbg_scene.cpp:13
flow — portable (Kokkos) geometric multigrid for the cut-cell (variable-openness) pressure Poisson.
Kokkos::View< double *, CCMem > CCField
Kokkos::View< const double *, CCMem > CCConst
Kokkos::View< double *, Kokkos::HostSpace > x
static void cg(CCField x, CCConst b, OpV AC, OpV AW, OpV AE, OpV AS, OpV AN, OpV AB, OpV AT, C3 e, Exch &&exchange, const Reduce &red, long gCells, int iters)
static Result setupAndSolve(CutcellMG &mg, C3 ext, C3 og, IVec< 3 > gs)
static void fillSource(CCField b, C3 ext, C3 og, IVec< 3 > gs)
static double source(int gx, int gy, int gz, IVec< 3 > gs)
static constexpr int G
static double compareBlock(const Result &d, const CutcellMG::Level &l0, const Result &ref, C3 re)