flow 0.4.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_cutcellmg_mpi.cpp
Go to the documentation of this file.
1// cfd-gpu — the PRODUCTION CutcellMG, multi-rank. Folds the distributed halo + reductions into the
2// real class (gated behind PECLET_FLOW_MPI) rather than a test-only copy.
3//
4// Solves the periodic cut-cell pressure Poisson by the real CutcellMG::solvePCG (MG-PCG: CG
5// preconditioned by one symmetric V-cycle) two ways: distributed (initMpi over MPI_COMM_WORLD) and
6// single-rank (init() on the full grid, the validated single-GPU path). Every rank runs the
7// single-rank reference redundantly and compares its own block. This proves CutcellMG.initMpi + the
8// gated fill()->exchange + dot/maxabs/removeMean->Allreduce produce the same field as the
9// single-GPU class, and (np=1) that the gating is byte-identical. Tolerance: np=1 is exact; np>1
10// agrees to ~1e-8 -- the MG-PCG is ADAPTIVE, so the inner-product Allreduce (summed in a different
11// order than the single-rank local sum) steers the Krylov path slightly differently and, with the
12// float-stored operator (residual floor ~1e-7 relative), the two converged solutions differ at
13// ~1e-9. That is an operation-order/roundoff difference, not a method change (the deterministic
14// V-cycle machinery is bit-tight to 1e-11 -- see test_distributed_mg). Build with -DCFD_MPI.
15#include <mpi.h>
16
17#include <cmath>
18#include <cstdio>
19#include <Kokkos_Core.hpp>
20
21#include "mac_cutcell_mg.hpp"
22#include "peclet/core/common/types.hpp"
23#include "peclet/core/decomp/block_decomposer.hpp"
24#include "peclet/core/halo/grid_halo_topology.hpp"
25
26using peclet::core::Index;
27using peclet::core::IVec;
32
33static constexpr int G = 1, NLEV = 4;
34static double source(int gx, int gy, int gz, IVec<3> gs) {
35 return std::sin(2.0 * M_PI * gx / gs[0]) +
36 std::cos(4.0 * M_PI * gy / gs[1]) * std::sin(2.0 * M_PI * gz / gs[2]);
37}
38
39// fill level-0 inner cells of an extended block (ext = inner+2G) given its global inner origin.
40static void fillSource(CCField b, C3 ext, C3 og, IVec<3> gs) {
41 auto hb = Kokkos::create_mirror_view(b);
42 for (std::size_t c = 0; c < b.extent(0); ++c)
43 hb(c) = 0.0;
44 for (int z = G; z < ext.z - G; ++z)
45 for (int y = G; y < ext.y - G; ++y)
46 for (int x = G; x < ext.x - G; ++x)
47 hb((long)x + (long)y * ext.x + (long)z * (long)ext.x * ext.y) =
48 source(x - G + og.x, y - G + og.y, z - G + og.z, gs);
49 Kokkos::deep_copy(b, hb);
50}
51
52int main(int argc, char** argv) {
53 MPI_Init(&argc, &argv);
54 Kokkos::initialize(argc, argv);
55 int fail = 0, size = 1, rank = 0;
56 {
57 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
58 MPI_Comm_size(MPI_COMM_WORLD, &size);
59 IVec<3> gs{32, 32, 32};
60
61 auto setupAndSolve = [&](CutcellMG& mg, C3 ext, C3 og) {
62 const std::size_t n = (std::size_t)ext.x * ext.y * ext.z;
63 CCField ox("ox", n), oy("oy", n), oz("oz", n); // all-fluid openness (periodic Laplacian)
64 Kokkos::deep_copy(ox, 1.0);
65 Kokkos::deep_copy(oy, 1.0);
66 Kokkos::deep_copy(oz, 1.0);
67 mg.setOpenness(CCConst(ox), CCConst(oy), CCConst(oz), 1.0, 1.0, 1.0);
68 CCField b("b", n), x("x", n), r("r", n), p("p", n), z("z", n), Ap("Ap", n);
69 fillSource(b, ext, og, gs);
70 mg.solvePCG(b, x, r, p, z, Ap, /*maxit=*/200, /*rtol=*/1e-10, 2, 2, 8);
71 auto hx = Kokkos::create_mirror_view(x);
72 Kokkos::deep_copy(hx, x);
73 return hx;
74 };
75
76 // distributed (real CutcellMG over MPI_COMM_WORLD)
77 CutcellMG mg;
78 mg.initMpi(gs[0], gs[1], gs[2], NLEV, MPI_COMM_WORLD);
79 CutcellMG::Level& l0 = mg.level(0);
80 auto hx = setupAndSolve(mg, l0.ext, l0.og);
81
82 // single-rank reference (the validated single-GPU path) on the full grid; every rank, compare
83 // its block
84 CutcellMG ref;
85 ref.init(gs[0], gs[1], gs[2], NLEV);
86 const C3 re{(int)gs[0] + 2 * G, (int)gs[1] + 2 * G, (int)gs[2] + 2 * G}, ro{0, 0, 0};
87 auto hr = setupAndSolve(ref, re, ro);
88
89 double maxdiff = 0.0;
90 for (int z = G; z < l0.ext.z - G; ++z)
91 for (int y = G; y < l0.ext.y - G; ++y)
92 for (int x = G; x < l0.ext.x - G; ++x) {
93 const int gx = x - G + l0.og.x, gy = y - G + l0.og.y, gz = z - G + l0.og.z;
94 const double a = hx((long)x + (long)y * l0.ext.x + (long)z * (long)l0.ext.x * l0.ext.y);
95 const double rr =
96 hr((long)(gx + G) + (long)(gy + G) * re.x + (long)(gz + G) * (long)re.x * re.y);
97 const double d = std::fabs(a - rr);
98 if (d > maxdiff)
99 maxdiff = d;
100 }
101 // np=1 (no Allreduce reordering) is bit-exact; np>1 is the float-op MG-PCG reduction-order
102 // floor (~1e-9).
103 const double tol = (size == 1) ? 1e-13 : 1e-8;
104 if (maxdiff > tol) {
105 ++fail;
106 std::fprintf(stderr, "[rank %d] max|distributed - single-rank CutcellMG| = %.3e (tol %.0e)\n",
107 rank, maxdiff, tol);
108 } else if (rank == 0)
109 std::printf(" max|distributed - single-rank| = %.3e (np=%d, tol %.0e)\n", maxdiff, size,
110 tol);
111 }
112 int totalFail = 0;
113 MPI_Allreduce(&fail, &totalFail, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
114 if (rank == 0) {
115 if (totalFail == 0)
116 std::printf("OK (np=%d): production CutcellMG MG-PCG distributed == single-rank\n", size);
117 else
118 std::fprintf(stderr, "FAILED (np=%d): %d ranks differ\n", size, totalFail);
119 }
120 Kokkos::finalize();
121 MPI_Finalize();
122 return totalFail == 0 ? 0 : 1;
123}
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)
flow — portable (Kokkos) geometric multigrid for the cut-cell (variable-openness) pressure Poisson.
Kokkos::View< double *, CCMem > CCField
Kokkos::View< const double *, CCMem > CCConst
int main(int argc, char **argv)
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 constexpr int NLEV