flow
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}
void setOpenness(CCConst ox, CCConst oy, CCConst oz, double idx2, double idy2, double idz2)
int solvePCG(CCField b, CCField x, CCField r, CCField p, CCField z, CCField Ap, int maxit, double rtol, int pre, int post, int bottom)
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