flow 0.4.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_ghost_projection_mpi.cpp
Go to the documentation of this file.
1// flow — multi-rank validation of the FULL directional ghost-cell projection
2// (set_ghost_projection, the 2nd-order scheme of ghost_projection.hpp — NOT face_interp mode 9).
3//
4// Lifted from single-rank (v1): gp-row ownership is by inner-block cell (buildGpOverlay runs over
5// this rank's inner cells, closures read the exchanged g=2 sdfGp/velocity halo), the BiCGStab
6// matvec stages the iterate on the solver's g=2 block so the overlay's +/-2 couplings see a
7// current halo, and the fragmentation guard runs on the allgathered GLOBAL sdf (a pocket can span
8// rank boundaries; every rank must agree on the main component).
9//
10// Sphere-packing Stokes flow, distributed (ORB blocks, initMpi) vs a full-grid single-rank
11// reference on rank 0, final u compared POINTWISE (pattern of test_multiphysics_mpi):
12// - staggered gp(2,2) well-separated spheres
13// - staggered gp(1,2) TOUCHING spheres (mixed/deferred closure + contact pockets -> the
14// global fragmentation guard is genuinely exercised across rank cuts)
15// - collocated gp(2,2) face_interp 0 (the only face map the ghost mode admits)
16// np=1 must be BIT-EXACT (catches ownership/halo mistakes cheaply); np>1 to the BiCGStab
17// reduction floor (dot-product Allreduce reorder; measured ~5e-12).
18//
19// Found by this test (fixed in flow_ibm project()): the binary-openness operator leaves
20// solid-centered cells as FREE variables; their Krylov-path-dependent phi leaked into near-wall
21// fluid u through the plain projectCorrect gradient — decomposition-dependent at ~1e-2 relative
22// while every gp diagnostic stayed clean. phi is now pinned to the design value 0 there.
23#include <mpi.h>
24
25#include <cmath>
26#include <cstdio>
27#include <cstdlib>
28#include <cstring>
29#include <Kokkos_Core.hpp>
30#include <vector>
31
32#include "flow_ibm.hpp"
33#include "peclet/core/common/types.hpp"
34#include "peclet/core/decomp/block_decomposer.hpp"
35
36using peclet::core::IVec;
39
40static constexpr int N = 32, STEPS = 15;
41static constexpr double RHO = 1.0, MU = 0.1, F = 1e-3, DT = 20.0;
42
43// 2x2x2 periodic sphere packing (flat x-fastest, negative inside).
44static std::vector<double> packingSdf(double rfrac) {
45 const double R = rfrac * N;
46 std::vector<double> sdf((std::size_t)N * N * N);
47 const double cs[2] = {0.25 * N, 0.75 * N};
48 for (int z = 0; z < N; ++z)
49 for (int y = 0; y < N; ++y)
50 for (int x = 0; x < N; ++x) {
51 double best = 1e30;
52 for (double sx : cs)
53 for (double sy : cs)
54 for (double sz : cs) {
55 auto wrap = [](double d) { return d - N * std::round(d / N); };
56 const double dx = wrap(x - sx), dy = wrap(y - sy), dz = wrap(z - sz);
57 best = std::min(best, std::sqrt(dx * dx + dy * dy + dz * dz) - R);
58 }
59 sdf[(std::size_t)x + (std::size_t)y * N + (std::size_t)z * N * N] = best;
60 }
61 return sdf;
62}
63
64template <class S>
65static void configure(S& s, const std::vector<double>& lsdf, int matrixOrder, int rhsOrder) {
66 s.setRho(RHO);
67 s.setMu(MU);
68 s.setDt(DT);
69 s.setBodyForce(F, 0, 0);
70 s.setAdvection(false);
71 s.setVelocityIterations(60);
72 s.setPressureLevels(4);
73 s.setPressurePcg(true, 300, 1e-9);
74 s.setGhostProjection(true, matrixOrder, rhsOrder); // BEFORE set_solid (overlay built there)
75 s.setSolid(lsdf, /*cutcell_pressure=*/true);
76}
77
78// Gather per-rank inner blocks into the global field on rank 0 (test_multiphysics_mpi pattern).
79static std::vector<double> gatherGlobal(const std::vector<double>& local, int ox, int oy, int oz,
80 int lnx, int lny, int lnz, int rank, int size) {
81 std::vector<double> global;
82 if (rank == 0)
83 global.assign((std::size_t)N * N * N, 0.0);
84 for (int r = 0; r < size; ++r) {
85 int meta[6] = {ox, oy, oz, lnx, lny, lnz};
86 if (r == 0) {
87 if (rank == 0)
88 for (int z = 0; z < lnz; ++z)
89 for (int y = 0; y < lny; ++y)
90 std::memcpy(&global[(std::size_t)ox + (std::size_t)(y + oy) * N +
91 (std::size_t)(z + oz) * N * N],
92 &local[(std::size_t)y * lnx + (std::size_t)z * lnx * lny],
93 (std::size_t)lnx * sizeof(double));
94 continue;
95 }
96 if (rank == r) {
97 MPI_Send(meta, 6, MPI_INT, 0, 100 + r, MPI_COMM_WORLD);
98 MPI_Send(local.data(), (int)local.size(), MPI_DOUBLE, 0, 200 + r, MPI_COMM_WORLD);
99 } else if (rank == 0) {
100 MPI_Recv(meta, 6, MPI_INT, r, 100 + r, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
101 std::vector<double> buf((std::size_t)meta[3] * meta[4] * meta[5]);
102 MPI_Recv(buf.data(), (int)buf.size(), MPI_DOUBLE, r, 200 + r, MPI_COMM_WORLD,
103 MPI_STATUS_IGNORE);
104 for (int z = 0; z < meta[5]; ++z)
105 for (int y = 0; y < meta[4]; ++y)
106 std::memcpy(&global[(std::size_t)meta[0] + (std::size_t)(y + meta[1]) * N +
107 (std::size_t)(z + meta[2]) * N * N],
108 &buf[(std::size_t)y * meta[3] + (std::size_t)z * meta[3] * meta[4]],
109 (std::size_t)meta[3] * sizeof(double));
110 }
111 }
112 return global;
113}
114
115static double relErr(const std::vector<double>& a, const std::vector<double>& b) {
116 double md = 0, mb = 0;
117 std::size_t am = 0;
118 for (std::size_t i = 0; i < b.size(); ++i) {
119 if (std::fabs(a[i] - b[i]) > md) {
120 md = std::fabs(a[i] - b[i]);
121 am = i;
122 }
123 mb = std::max(mb, std::fabs(b[i]));
124 }
125 if (std::getenv("GP_MPI_DEBUG"))
126 std::printf(" argmax |a-b| at (%d,%d,%d)\n", (int)(am % N), (int)((am / N) % N),
127 (int)(am / ((std::size_t)N * N)));
128 return md / (mb + 1e-300);
129}
130
131// One distributed-vs-reference comparison for a solver type + gp order pair + geometry.
132template <class S>
133static int runCase(const char* name, double rfrac, int mo, int ro, int rank, int size) {
134 const std::vector<double> gsdf = packingSdf(rfrac);
135 peclet::core::decomp::BlockDecomposer<3> dec(static_cast<std::size_t>(size), IVec<3>{N, N, N});
136 auto blk = dec.block(rank);
137 const int ox = (int)blk.origin[0], oy = (int)blk.origin[1], oz = (int)blk.origin[2];
138 const int lnx = (int)blk.size[0], lny = (int)blk.size[1], lnz = (int)blk.size[2];
139 std::vector<double> lsdf((std::size_t)lnx * lny * lnz);
140 for (int z = 0; z < lnz; ++z)
141 for (int y = 0; y < lny; ++y)
142 for (int x = 0; x < lnx; ++x)
143 lsdf[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] =
144 gsdf[(std::size_t)(x + ox) + (std::size_t)(y + oy) * N + (std::size_t)(z + oz) * N * N];
145
146 S sd(lnx, lny, lnz);
147 sd.initMpi(N, N, N, MPI_COMM_WORLD);
148 configure(sd, lsdf, mo, ro);
149 for (int it = 0; it < STEPS; ++it)
150 sd.step();
151 auto gu = gatherGlobal(sd.getVelocity(0), ox, oy, oz, lnx, lny, lnz, rank, size);
152 const double resd = sd.maxOpenDivergence(); // distributed diagnostic (collective)
153
154 int fail = 0;
155 if (rank == 0) {
156 S ref(N, N, N);
157 configure(ref, gsdf, mo, ro);
158 for (int it = 0; it < STEPS; ++it)
159 ref.step();
160 const double eu = relErr(gu, ref.getVelocity(0));
161 // np=1 is the strongest gate: the distributed path (halo exchange in place of the periodic
162 // wrap, staged matvec, allgathered fragmentation guard) must be BIT-exact.
163 // measured floors (host-openmp): np=1 exactly 0; np>1 <= ~5e-12 (BiCGStab dot-product
164 // Allreduce reorder only — the decoupled-phi pinning removed every larger term).
165 const double tol = (size == 1) ? 0.0 : 1e-9;
166 const bool ok = eu <= tol;
167 std::printf(" [%-12s np=%d] u rel=%.3e tol=%.0e div(d)=%.3e div(ref)=%.3e %s\n", name,
168 size, eu, tol, resd, ref.maxOpenDivergence(), ok ? "OK" : "FAIL");
169 if (!ok)
170 fail = 1;
171 }
172 MPI_Bcast(&fail, 1, MPI_INT, 0, MPI_COMM_WORLD);
173 return fail;
174}
175
176int main(int argc, char** argv) {
177 MPI_Init(&argc, &argv);
178 Kokkos::initialize(argc, argv);
179 int fail = 0;
180 {
181 int rank = 0, size = 1;
182 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
183 MPI_Comm_size(MPI_COMM_WORLD, &size);
184 fail |= runCase<Stag>("stag-gp22", 0.18, 2, 2, rank, size);
185 fail |= runCase<Stag>("stag-gp12-tch", 0.26, 1, 2, rank, size);
186 fail |= runCase<Colo>("colo-gp22", 0.18, 2, 2, rank, size);
187 if (rank == 0)
188 std::printf("GHOST PROJECTION MPI (np=%d): %s\n", size, fail ? "FAIL" : "PASS");
189 }
190 Kokkos::finalize();
191 MPI_Finalize();
192 return fail;
193}
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
Solver< Staggered > IbmSolver
static void configure(S &s, const std::vector< double > &lsdf, int matrixOrder, int rhsOrder)
int main(int argc, char **argv)
static std::vector< double > gatherGlobal(const std::vector< double > &local, int ox, int oy, int oz, int lnx, int lny, int lnz, int rank, int size)
static constexpr double RHO
static constexpr double MU
static constexpr int STEPS
static constexpr int N
static constexpr double DT
static constexpr double F
static int runCase(const char *name, double rfrac, int mo, int ro, int rank, int size)
static std::vector< double > packingSdf(double rfrac)
static double relErr(const std::vector< double > &a, const std::vector< double > &b)