flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_wall_slip_mpi.cpp
Go to the documentation of this file.
1// flow — WO-V6b gate 6: the NAVIER SLIP wall closure under MPI.
2//
3// The single-rank gate (tests/study/wall_slip.py) is a slip POISEUILLE: a slit of width H between
4// two flat SDF walls at QUARTER-INTEGER coordinates, driven by a uniform body force G, whose exact
5// steady Stokes solution with slip length lambda at both walls is the QUADRATIC
6//
7// u(z) = (G / 2 mu) * ( (z - z0)(z1 - z) + lambda H ), H = z1 - z0,
8//
9// which satisfies the Robin condition exactly, so the Robust-Scaled closure must reproduce it.
10// Here the same scene is run distributed and compared POINTWISE against a full-grid single-rank
11// reference on rank 0 (the tests/kokkos_mpi pattern): np = 1 BITWISE, np > 1 to the reduction-order
12// floor, at lambda = 0 (the no-slip control, which also gates that the lambda = 0 branch is inert
13// under MPI) and lambda = 0.1 cells.
14//
15// THE DECOMPOSITION CUTS BOTH WALLS' CLOSURES. The ORB splits the 40-cell z axis into
16// [0,16) [16,24) [24,32) [32,40) at np = 4, and the walls are placed at z = 16.25 and z = 32.25 so
17// that each wall's cut cell and its SOLID neighbour land on opposite sides of a rank boundary:
18// the low wall's cut cell k = 16 (theta = 0.25) is the first cell of block 1 with its solid
19// neighbour k = 15 in block 0, and the high wall's cut cell k = 31 (theta = 0.75) is the last cell
20// of block 2 with its solid neighbour k = 32 in block 3. Both Robin closures therefore read an
21// exchanged ghost. At np = 2 (blocks [0,24) [24,40)) the cut falls in the fluid interior. The test
22// prints the z-blocks and asserts that z is cut at all.
23#include <mpi.h>
24
25#include <cmath>
26#include <cstdio>
27#include <cstring>
28#include <Kokkos_Core.hpp>
29#include <vector>
30
31#include "flow_ibm.hpp"
32#include "peclet/core/common/types.hpp"
33#include "peclet/core/decomp/block_decomposer.hpp"
34
36
37static constexpr int NX = 8, NY = 8, NZ = 40;
38static constexpr double Z0 = 16.25, Z1 = 32.25; // quarter-integer wall placement (WO-S finding 5)
39static constexpr double MU = 1.0, RHO = 1.0, DT = 50.0, GFORCE = 1e-3;
40static constexpr int STEPS = 200;
41static constexpr std::size_t GCELLS = (std::size_t)NX * NY * NZ;
42
43static double sdfAt(int, int, int z) {
44 const double zc = z + 0.5;
45 return std::fmin(zc - Z0, Z1 - zc);
46}
47
48static std::vector<double> blockSdf(int oz, int lnx, int lny, int lnz) {
49 std::vector<double> v((std::size_t)lnx * lny * lnz);
50 for (int z = 0; z < lnz; ++z)
51 for (int y = 0; y < lny; ++y)
52 for (int x = 0; x < lnx; ++x)
53 v[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] = sdfAt(0, 0, z + oz);
54 return v;
55}
56
57static void configure(IbmSolver& s, double lam, int oz, int lnx, int lny, int lnz) {
58 s.setRho(RHO);
59 s.setMu(MU);
60 s.setDt(DT);
61 s.setAdvection(false); // Stokes: the analytic profile is the steady Stokes solution
62 s.setBodyForce(GFORCE, 0.0, 0.0);
63 s.setPressurePcg(true, 400, 1e-12);
65 s.setSolid(blockSdf(oz, lnx, lny, lnz), /*cutcellPressure=*/true);
66 if (lam > 0.0)
67 s.setWallSlipLength(lam);
68}
69
70static std::vector<double> gatherGlobal(const std::vector<double>& local, int ox, int oy, int oz,
71 int lnx, int lny, int lnz, int rank, int size) {
72 std::vector<double> global;
73 if (rank == 0)
74 global.assign(GCELLS, 0.0);
75 for (int r = 0; r < size; ++r) {
76 int meta[6] = {ox, oy, oz, lnx, lny, lnz};
77 if (r == 0) {
78 if (rank == 0)
79 for (int z = 0; z < lnz; ++z)
80 for (int y = 0; y < lny; ++y)
81 std::memcpy(&global[(std::size_t)ox + (std::size_t)(y + oy) * NX +
82 (std::size_t)(z + oz) * NX * NY],
83 &local[(std::size_t)y * lnx + (std::size_t)z * lnx * lny],
84 (std::size_t)lnx * sizeof(double));
85 continue;
86 }
87 if (rank == r) {
88 MPI_Send(meta, 6, MPI_INT, 0, 100 + r, MPI_COMM_WORLD);
89 MPI_Send(local.data(), (int)local.size(), MPI_DOUBLE, 0, 200 + r, MPI_COMM_WORLD);
90 } else if (rank == 0) {
92 std::vector<double> buf((std::size_t)meta[3] * meta[4] * meta[5]);
93 MPI_Recv(buf.data(), (int)buf.size(), MPI_DOUBLE, r, 200 + r, MPI_COMM_WORLD,
95 for (int z = 0; z < meta[5]; ++z)
96 for (int y = 0; y < meta[4]; ++y)
97 std::memcpy(&global[(std::size_t)meta[0] + (std::size_t)(y + meta[1]) * NX +
98 (std::size_t)(z + meta[2]) * NX * NY],
99 &buf[(std::size_t)y * meta[3] + (std::size_t)z * meta[3] * meta[4]],
100 (std::size_t)meta[3] * sizeof(double));
101 }
102 }
103 return global;
104}
105
106static double maxAbsDiff(const std::vector<double>& a, const std::vector<double>& b) {
107 double m = 0; // NaN-propagating (WO-R2)
108 for (std::size_t i = 0; i < b.size(); ++i) {
109 const double d = std::fabs(a[i] - b[i]);
110 if (!(d == d))
111 return d;
112 m = std::fmax(m, d);
113 }
114 return m;
115}
116static double maxAbs(const std::vector<double>& a) {
117 double m = 0;
118 for (double v : a)
119 m = std::fmax(m, std::fabs(v));
120 return m;
121}
122
123// max |u - u_exact| over the fluid column, relative to the peak of the exact profile.
124static double analyticError(const std::vector<double>& u, double lam) {
125 const double H = Z1 - Z0;
126 double e = 0, s = 0;
127 for (int z = 0; z < NZ; ++z) {
128 const double zc = z + 0.5;
129 if (zc <= Z0 || zc >= Z1)
130 continue;
131 const double ue = (GFORCE / (2.0 * MU)) * ((zc - Z0) * (Z1 - zc) + lam * H);
132 const std::size_t i =
133 (std::size_t)(NX / 2) + (std::size_t)(NY / 2) * NX + (std::size_t)z * NX * NY;
134 e = std::fmax(e, std::fabs(u[i] - ue));
135 s = std::fmax(s, std::fabs(ue));
136 }
137 return e / (s + 1e-300);
138}
139
140int main(int argc, char** argv) {
141 MPI_Init(&argc, &argv);
142 Kokkos::initialize(argc, argv);
143 int fail = 0;
144 {
145 int rank = 0, size = 1;
148
149 auto dec = peclet::flow::CutcellMG::decomposition(static_cast<std::size_t>(size), NX, NY, NZ);
150 auto blk = dec.block(rank);
151 const int ox = (int)blk.origin[0], oy = (int)blk.origin[1], oz = (int)blk.origin[2];
152 const int lnx = (int)blk.size[0], lny = (int)blk.size[1], lnz = (int)blk.size[2];
153 const int gn[3] = {NX, NY, NZ};
154 bool cut[3] = {false, false, false};
155 for (const auto& sz : dec.sizes())
156 for (int a = 0; a < 3; ++a)
157 if ((int)sz[a] != gn[a])
158 cut[a] = true;
159 if (rank == 0)
160 if (rank == 0) {
161 std::printf(
162 "WALL-SLIP MPI np=%d grid %dx%dx%d block %dx%dx%d cut axes: %s%s%s "
163 "walls z = %.2f / %.2f z-blocks:",
164 size, NX, NY, NZ, lnx, lny, lnz, cut[0] ? "x" : "", cut[1] ? "y" : "",
165 cut[2] ? "z" : "", Z0, Z1);
166 for (int r = 0; r < size; ++r)
167 std::printf(
168 " [%d,%d)", (int)dec.block((std::size_t)r).origin[2],
169 (int)(dec.block((std::size_t)r).origin[2] + dec.block((std::size_t)r).size[2]));
170 std::printf("\n");
171 }
172 if (size > 1 && !cut[2]) {
173 if (rank == 0)
174 std::printf(
175 " FAIL — the decomposition does NOT cut the walled z axis; this test exists "
176 "to gate the Navier closure across a rank boundary\n");
177 fail = 1;
178 }
179
180 const double lambdas[] = {0.0, 0.1};
181 for (double lam : lambdas) {
183 sd.initMpi(dec, MPI_COMM_WORLD);
184 configure(sd, lam, oz, lnx, lny, lnz);
185 for (int it = 0; it < STEPS; ++it)
186 sd.step();
187 std::vector<double> gu[3];
188 for (int comp = 0; comp < 3; ++comp)
189 gu[comp] = gatherGlobal(sd.getVelocity(comp), ox, oy, oz, lnx, lny, lnz, rank, size);
190 if (rank == 0) {
191 IbmSolver ref(NX, NY, NZ);
192 configure(ref, lam, 0, NX, NY, NZ);
193 // The distributed solve keeps the host GraphAMG bottom (doc/vof_step_performance_design.md
194 // §13.9 Q-D7); on a GPU the single-rank solver would take the device bottom, so the np = 1
195 // bit-exact gate pins the reference to the engine the distributed path runs.
196 ref.setPressureBottomSolver(2); // 'algebraic'
197 for (int it = 0; it < STEPS; ++it)
198 ref.step();
199 double du = 0, umag = 0;
200 for (int comp = 0; comp < 3; ++comp) {
201 du = std::fmax(du, maxAbsDiff(gu[comp], ref.getVelocity(comp)));
202 umag = std::fmax(umag, maxAbs(ref.getVelocity(comp)));
203 }
204 const double tol = (size == 1) ? 0.0 : std::fmax(1e-15, 1e-11 * umag);
205 const double aerr = analyticError(gu[0], lam);
206 // The analytic tolerance is the FLOAT momentum-operator storage floor, not the scheme's
207 // consistency error: the closure coefficients are stored in float, and the SAME scene at
208 // lambda = 0 misses the (exact) no-slip parabola by 1.15e-6 relative. Gate both at 5e-6.
209 const bool ok = du <= tol && aerr < 5e-6;
210 std::printf(
211 " [lambda=%.2f np=%d] du=%.3e (|u|=%.3e, tol %.1e) | analytic rel err "
212 "%.3e (tol 5.0e-06) %s\n",
213 lam, size, du, umag, tol, aerr, ok ? "OK" : "FAIL");
214 if (!ok)
215 fail = 1;
216 }
218 }
219 if (rank == 0)
220 std::printf("WALL-SLIP MPI (np=%d): %s\n", size, fail ? "FAIL" : "PASS");
221 }
222 Kokkos::finalize();
223 MPI_Finalize();
224 return fail;
225}
void setSolid(const std::vector< double > &sdfInner, bool cutcellPressure)
Host entry point: upload the inner SDF once and delegate.
void setAdvection(bool on)
void setVelocityIterations(int it)
void setWallSlipLength(double lambdaCells)
WO-V6b – the VELOCITY half of the dynamic contact line.
void setBodyForce(double fx, double fy, double fz)
Body force per unit volume, in the caller's units (e.g. a mean pressure gradient, or rho*g).
std::vector< double > getVelocity(int c)
void setPressurePcg(bool on, int maxit, double rtol)
void setMu(double m)
Dynamic viscosity, in the caller's units.
void setRho(double r)
Fluid density, in the caller's units.
int main()
Definition dbg_scene.cpp:13
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
static double maxAbs(const std::vector< double > &a)
static constexpr int NY
static constexpr double GFORCE
static double analyticError(const std::vector< double > &u, double lam)
static void configure(IbmSolver &s, double lam, int oz, int lnx, int lny, int lnz)
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 Z0
static constexpr double RHO
static constexpr double MU
static constexpr int STEPS
static double maxAbsDiff(const std::vector< double > &a, const std::vector< double > &b)
static constexpr double DT
static constexpr double Z1
static double sdfAt(int, int, int z)
static constexpr int NX
static constexpr std::size_t GCELLS
static constexpr int NZ
static std::vector< double > blockSdf(int oz, int lnx, int lny, int lnz)