flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
probe_vof_packing.cpp
Go to the documentation of this file.
1// DIAGNOSTIC PROBE — NOT a gate, not a ctest (EXCLUDE_FROM_ALL). This is the instrument that
2// answered docs/wo_vof_mpi_parity_gates.md; it is committed so the trace behind
3// `test_vof_bc_mpi.cpp`'s packing tolerances can be reproduced instead of taken on trust.
4//
5// cmake --build build_dev --target probe_vof_packing
6// OMP_NUM_THREADS=8 mpirun -np 2 ./build_dev/tests/kokkos_mpi/probe_vof_packing
7//
8// It runs `configurePacking`'s scene distributed and single-rank IN LOCKSTEP, gathering the colour
9// after every step, so "does the disagreement appear at once or grow from round-off" is answered by
10// a curve rather than one end-of-run number. (It appears at once: 4.4e-16 after step 0, 4.795e-09
11// after step 1, decaying thereafter — a branch flipping, not an accumulation.)
12//
13// Per step it prints the max |C_np - C_ref| and where; how many cells exceed 1e-11; the max
14// difference of u, v, w and p (which stay at the allreduce floor while the colour does not); the
15// census of the two threshold predicates that could have explained it and do not (the `C > 0.5`
16// Weymouth-Yue dilation flag and `wyIsMixed`'s wisp tolerance); and both pressure iteration counts.
17//
18// Knobs:
19// PROBE_STEPS=n how many steps (default 40, as the test)
20// PROBE_KINEMATIC=1 prescribe a UNIFORM w and call advectVof() instead of step() — no pressure
21// =2 prescribe a 3-D VARYING analytic velocity instead. This is the decisive one:
22// it is bitwise at every np and thread count, which is what proves the VoF
23// path innocent and became the test's `packing-kin` case. A uniform field
24// (=1) proves much less — every value being equal hides a mis-filled ghost.
25// PROBE_ULP=1 perturb the REFERENCE's initial colour by one ulp in one far cell
26// PROBE_NOSOLID=1 drop the packing SDF
27// PROBE_RTOL=x pin the pressure rtol (default: the solver's own 1e-10)
28// PROBE_WISP=x set the wisp tolerance (default 1e-8, what enable_vof sets)
29// PROBE_LIST=1 list every cell differing by more than 1e-11, at full precision
30// PROBE_CELL=1 dump the corner face velocities at full precision
31// PROBE_PAD=1 per-field max difference over the PADDED block — ghosts included, which
32// getField() cannot show
33// PROBE_PADCELL=1 list every differing padded value near the corner (14..15, 0, 0)
34#include <mpi.h>
35
36#include <algorithm>
37#include <cmath>
38#include <cstdio>
39#include <cstdlib>
40#include <cstring>
41#include <Kokkos_Core.hpp>
42#include <vector>
43
44#include "flow_ibm.hpp"
45#include "peclet/core/common/types.hpp"
46#include "peclet/core/decomp/block_decomposer.hpp"
47
49
50static constexpr int NX = 16, NY = 16, NZ = 32;
51static constexpr std::size_t GCELLS = (std::size_t)NX * NY * NZ;
52
53static std::vector<double> gatherGlobal(const std::vector<double>& local, int ox, int oy, int oz,
54 int lnx, int lny, int lnz, int rank, int size) {
55 std::vector<double> global;
56 if (rank == 0)
57 global.assign(GCELLS, 0.0);
58 for (int r = 0; r < size; ++r) {
59 int meta[6] = {ox, oy, oz, lnx, lny, lnz};
60 if (r == 0) {
61 if (rank == 0)
62 for (int z = 0; z < lnz; ++z)
63 for (int y = 0; y < lny; ++y)
64 std::memcpy(&global[(std::size_t)ox + (std::size_t)(y + oy) * NX +
65 (std::size_t)(z + oz) * NX * NY],
66 &local[(std::size_t)y * lnx + (std::size_t)z * lnx * lny],
67 (std::size_t)lnx * sizeof(double));
68 continue;
69 }
70 if (rank == r) {
71 MPI_Send(meta, 6, MPI_INT, 0, 100 + r, MPI_COMM_WORLD);
72 MPI_Send(local.data(), (int)local.size(), MPI_DOUBLE, 0, 200 + r, MPI_COMM_WORLD);
73 } else if (rank == 0) {
75 std::vector<double> buf((std::size_t)meta[3] * meta[4] * meta[5]);
76 MPI_Recv(buf.data(), (int)buf.size(), MPI_DOUBLE, r, 200 + r, MPI_COMM_WORLD,
78 for (int z = 0; z < meta[5]; ++z)
79 for (int y = 0; y < meta[4]; ++y)
80 std::memcpy(&global[(std::size_t)meta[0] + (std::size_t)(y + meta[1]) * NX +
81 (std::size_t)(z + meta[2]) * NX * NY],
82 &buf[(std::size_t)meta[3] * 0 + (std::size_t)y * meta[3] +
83 (std::size_t)z * meta[3] * meta[4]],
84 (std::size_t)meta[3] * sizeof(double));
85 }
86 }
87 return global;
88}
89
90static std::vector<double> packingSdf(int ox, int oy, int oz, int lnx, int lny, int lnz) {
91 const double sp[5][4] = {{4.5, 4.5, 11, 3.4},
92 {11.5, 11.5, 11, 3.4},
93 {4.5, 11.5, 20, 3.4},
94 {11.5, 4.5, 20, 3.4},
95 {8.0, 8.0, (double)NZ, 3.6}};
96 std::vector<double> f((std::size_t)lnx * lny * lnz, 1e30);
97 for (int z = 0; z < lnz; ++z)
98 for (int y = 0; y < lny; ++y)
99 for (int x = 0; x < lnx; ++x) {
100 double d = 1e30;
101 for (const auto& q : sp) {
102 const double dx = x + ox + 0.5 - q[0], dy = y + oy + 0.5 - q[1], dz = z + oz + 0.5 - q[2];
103 d = std::fmin(d, std::sqrt(dx * dx + dy * dy + dz * dz) - q[3]);
104 }
105 f[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] = d;
106 }
107 return f;
108}
109
110static double envd(const char* k, double dflt) {
111 const char* v = std::getenv(k);
112 return v ? std::atof(v) : dflt;
113}
114
115static void configurePacking(IbmSolver& s, int ox, int oy, int oz, int lnx, int lny, int lnz,
116 bool perturb = false) {
117 s.setRho(1.0);
118 s.setMu(0.5);
119 s.setDt(0.1);
120 for (int f = 0; f < 4; ++f)
121 s.setDomainBc(f, 1, 0, 0, 0);
122 s.setDomainBc(4, 2, 0.0, 0.0, 0.5);
123 s.setDomainBc(5, 3, 0, 0, 0);
127 if (!std::getenv("PROBE_NOSOLID"))
128 s.setSolid(packingSdf(ox, oy, oz, lnx, lny, lnz), true);
129 s.enableVof();
130 const double rtol = envd("PROBE_RTOL", 0.0);
131 if (rtol > 0.0)
132 s.setPressurePcg(true, 400, rtol);
133 const double wisp = envd("PROBE_WISP", -1.0);
134 if (wisp >= 0.0)
136 std::vector<double> c0((std::size_t)lnx * lny * lnz, 0.0);
137 for (int z = 0; z < lnz; ++z)
138 if (z + oz >= NZ / 2)
139 for (int y = 0; y < lny; ++y)
140 for (int x = 0; x < lnx; ++x)
141 c0[(std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny] = 1.0;
142 // PROBE_ULP: perturb the initial colour of ONE cell at the far +z end by one ulp -- a change
143 // of 1.1e-16 in one cell of the initial data, far smaller than any decomposition's
144 // reduction-order noise, and applied to the SINGLE-RANK run so no MPI is involved at all.
145 if (perturb) {
146 const std::size_t kp =
147 (std::size_t)0 + (std::size_t)0 * lnx + (std::size_t)(lnz - 1) * lnx * lny;
148 c0[kp] = std::nextafter(1.0, 0.0);
149 }
150 s.setVof(c0);
151 s.setVofInflow(4, 1.0);
152 s.setVofBackflow(5, 0.0);
153 if (std::getenv("PROBE_KINEMATIC")) {
154 // Drive the colour with a PRESCRIBED velocity built from GLOBAL cell centres: the cut-cell
155 // geometry and the open boundaries stay, the pressure/velocity solve is gone. Isolates the VoF
156 // path from the flow solve. PROBE_KINEMATIC=2 makes it spatially VARYING -- a uniform field
157 // hides every ghost-fill and bridge difference, because all the values are equal.
158 const bool vary = std::atoi(std::getenv("PROBE_KINEMATIC")) >= 2;
159 std::vector<double> fu((std::size_t)lnx * lny * lnz, 0.0), fv(fu), fw(fu);
160 for (int z = 0; z < lnz; ++z)
161 for (int y = 0; y < lny; ++y)
162 for (int x = 0; x < lnx; ++x) {
163 const std::size_t k = (std::size_t)x + (std::size_t)y * lnx + (std::size_t)z * lnx * lny;
164 const double gx = x + ox + 0.5, gy = y + oy + 0.5, gz = z + oz + 0.5;
165 if (!vary) {
166 fw[k] = 0.5;
167 continue;
168 }
169 const double pi = 3.14159265358979323846;
170 fu[k] = 0.10 * std::sin(2 * pi * gx / NX) * std::cos(2 * pi * gy / NY) *
171 std::cos(pi * gz / NZ);
172 fv[k] = 0.10 * std::cos(2 * pi * gx / NX) * std::sin(2 * pi * gy / NY) *
173 std::cos(pi * gz / NZ);
174 fw[k] = 0.50 + 0.10 * std::cos(2 * pi * gx / NX) * std::cos(2 * pi * gy / NY) *
175 std::sin(pi * gz / NZ);
176 }
177 s.setField("u", fu);
178 s.setField("v", fv);
179 s.setField("w", fw);
180 }
181}
182
183int main(int argc, char** argv) {
184 MPI_Init(&argc, &argv);
185 Kokkos::initialize(argc, argv);
186 {
187 int rank = 0, size = 1;
190 auto dec = peclet::flow::CutcellMG::decomposition(static_cast<std::size_t>(size), NX, NY, NZ);
191 auto blk = dec.block(rank);
192 const int ox = (int)blk.origin[0], oy = (int)blk.origin[1], oz = (int)blk.origin[2];
193 const int lnx = (int)blk.size[0], lny = (int)blk.size[1], lnz = (int)blk.size[2];
194 const int steps = (int)envd("PROBE_STEPS", 40);
195 const double wisp = envd("PROBE_WISP", 1e-8);
196
198 sd.initMpi(dec, MPI_COMM_WORLD);
199 configurePacking(sd, ox, oy, oz, lnx, lny, lnz);
200
201 IbmSolver* ref = nullptr;
202 if (rank == 0) {
203 ref = new IbmSolver(NX, NY, NZ);
204 configurePacking(*ref, 0, 0, 0, NX, NY, NZ, std::getenv("PROBE_ULP") != nullptr);
205 std::printf("PROBE packing np=%d steps=%d wisp=%.3e rtol=%s solid=%s\n", size, steps, wisp,
206 std::getenv("PROBE_RTOL") ? std::getenv("PROBE_RTOL") : "default",
207 std::getenv("PROBE_NOSOLID") ? "off" : "on");
208 std::printf("%5s %11s %-16s %10s %11s %11s %11s %7s\n", "step", "dc", "argmax(x,y,z)",
209 "n>1e-11", "C_np", "C_ref", "dc|mixed", "its d/r");
210 }
211 if (rank == 0) {
212 std::printf("fields:");
213 for (const auto& n : sd.fieldNames())
214 std::printf(" %s", n.c_str());
215 std::printf("\n");
216 }
217 const char* flds[4] = {"u", "v", "w", "p"};
218 const bool kin = std::getenv("PROBE_KINEMATIC") != nullptr;
219 for (int i = 0; i < steps; ++i) {
220 if (kin)
221 sd.advectVof();
222 else
223 sd.step();
224 const std::vector<double> gc =
225 gatherGlobal(sd.getVof(), ox, oy, oz, lnx, lny, lnz, rank, size);
226 std::vector<std::vector<double>> gf;
227 for (int f = 0; f < 4; ++f)
228 gf.push_back(gatherGlobal(sd.getField(flds[f]), ox, oy, oz, lnx, lny, lnz, rank, size));
230 const double divd = sd.maxOpenDivergence();
231 if (rank == 0) {
232 if (kin)
233 ref->advectVof();
234 else
235 ref->step();
236 const std::vector<double> rc = ref->getVof();
237 double dfld[4] = {0, 0, 0, 0}, mfld[4] = {0, 0, 0, 0};
238 for (int f = 0; f < 4; ++f) {
239 const std::vector<double> rf = ref->getField(flds[f]);
240 for (std::size_t k = 0; k < GCELLS; ++k) {
241 dfld[f] = std::fmax(dfld[f], std::fabs(gf[f][k] - rf[k]));
242 mfld[f] = std::fmax(mfld[f], std::fabs(rf[k]));
243 }
244 }
245 long nflip05 = 0;
246 double nearest05 = 1.0;
247 for (std::size_t k = 0; k < GCELLS; ++k) {
248 if ((gc[k] > 0.5) != (rc[k] > 0.5))
249 ++nflip05;
250 nearest05 = std::fmin(nearest05, std::fabs(rc[k] - 0.5));
251 }
252 double dc = 0;
253 std::size_t am = 0;
254 long nbad = 0, nflip = 0;
255 for (std::size_t k = 0; k < GCELLS; ++k) {
256 const double a = gc[k], b = rc[k];
257 const double d = std::fabs(a - b);
258 if (d > dc) {
259 dc = d;
260 am = k;
261 }
262 if (d > 1e-11)
263 ++nbad;
264 const bool ma = (a > wisp && a < 1.0 - wisp), mb = (b > wisp && b < 1.0 - wisp);
265 if (ma != mb)
266 ++nflip;
267 }
268 const int ax = (int)(am % NX), ay = (int)((am / NX) % NY), az = (int)(am / (NX * NY));
269 std::printf(
270 "%4d dc %10.3e @(%2d,%2d,%2d) C %.5f n>tol %3ld | du %8.2e dv %8.2e dw %8.2e "
271 "dp %8.2e (|p| %8.2e) | flip05 %ld |C-.5|min %8.2e wispflip %ld | its %ld/%ld "
272 "div %8.2e\n",
273 i, dc, ax, ay, az, rc[am], nbad, dfld[0], dfld[1], dfld[2], dfld[3], mfld[3], nflip05,
274 nearest05, nflip, itd, ref->lastPressureIterations(), divd);
275 // PADDED comparison, ghosts included: rank 0's block starts at the global origin, so its
276 // padded (i,j,k) matches the reference's padded (i,j,k) wherever both exist. This is the
277 // only way to see a GHOST that differs -- getField() returns inner cells only.
278 if (std::getenv("PROBE_PAD")) {
279 const auto sh = sd.blockShape();
280 const auto rh = ref->blockShape();
281 const char* pf[5] = {"u", "v", "w", "p", "C"};
282 for (int f = 0; f < 5; ++f) {
283 auto vd = sd.fieldView(pf[f]);
284 auto vr = ref->fieldView(pf[f]);
285 auto hd = Kokkos::create_mirror_view(vd);
286 Kokkos::deep_copy(hd, vd);
287 auto hr2 = Kokkos::create_mirror_view(vr);
288 Kokkos::deep_copy(hr2, vr);
289 double md = 0;
290 int bi = -1, bj = -1, bk = -1;
291 bool bg = false;
292 const int G2 = sd.ghostWidth();
293 for (int k = 0; k < std::min(sh[2], rh[2]); ++k)
294 for (int j = 0; j < std::min(sh[1], rh[1]); ++j)
295 for (int ii = 0; ii < std::min(sh[0], rh[0]); ++ii) {
296 const double a =
297 hd((std::size_t)ii + (std::size_t)j * sh[0] + (std::size_t)k * sh[0] * sh[1]);
298 const double b = hr2((std::size_t)ii + (std::size_t)j * rh[0] +
299 (std::size_t)k * rh[0] * rh[1]);
300 const double d = std::fabs(a - b);
301 if (d > md) {
302 md = d;
303 bi = ii - G2;
304 bj = j - G2;
305 bk = k - G2;
306 bg = (ii < G2 || j < G2 || k < G2 || ii >= sh[0] - G2 || j >= sh[1] - G2 ||
307 k >= sh[2] - G2);
308 }
309 }
310 std::printf(" pad %s max %.3e @(%3d,%3d,%3d) %s\n", pf[f], md, bi, bj, bk,
311 bg ? "GHOST" : "inner");
312 }
313 }
314 if (std::getenv("PROBE_PADCELL")) {
315 const auto sh = sd.blockShape();
316 const auto rh = ref->blockShape();
317 const int G2 = sd.ghostWidth();
318 const char* pf[4] = {"u", "v", "w", "C"};
319 for (int f = 0; f < 4; ++f) {
320 auto vd = sd.fieldView(pf[f]);
321 auto vr = ref->fieldView(pf[f]);
322 auto hd = Kokkos::create_mirror_view(vd);
323 Kokkos::deep_copy(hd, vd);
324 auto h2 = Kokkos::create_mirror_view(vr);
325 Kokkos::deep_copy(h2, vr);
326 for (int z = -G2; z <= 1; ++z)
327 for (int y = -G2; y <= 1; ++y)
328 for (int x = 13; x < 13 + 3 + G2; ++x) {
329 const int i = x + G2, j = y + G2, k = z + G2;
330 if (i >= sh[0] || j >= sh[1] || k >= sh[2])
331 continue;
332 const double a =
333 hd((std::size_t)i + (std::size_t)j * sh[0] + (std::size_t)k * sh[0] * sh[1]);
334 const double b =
335 h2((std::size_t)i + (std::size_t)j * rh[0] + (std::size_t)k * rh[0] * rh[1]);
336 if (a != b)
337 std::printf(" DIFF %s (%3d,%3d,%3d) np %.17g ref %.17g d %.3e\n", pf[f], x,
338 y, z, a, b, a - b);
339 }
340 }
341 }
342 if (std::getenv("PROBE_CELL")) {
343 const char* nm[3] = {"u", "v", "w"};
344 std::vector<std::vector<double>> rr;
345 for (int f = 0; f < 3; ++f)
346 rr.push_back(ref->getField(nm[f]));
347 for (int z = 0; z <= 1; ++z)
348 for (int y = 0; y <= 1; ++y)
349 for (int x = 13; x <= 15; ++x) {
350 const std::size_t k =
351 (std::size_t)x + (std::size_t)y * NX + (std::size_t)z * NX * NY;
352 std::printf(" (%2d,%2d,%2d)", x, y, z);
353 for (int f = 0; f < 3; ++f)
354 std::printf(" %s np %.17g ref %.17g", nm[f], gf[f][k], rr[f][k]);
355 std::printf("\n");
356 }
357 }
358 if (std::getenv("PROBE_LIST")) {
359 long shown = 0;
360 for (std::size_t k = 0; k < GCELLS && shown < 24; ++k)
361 if (std::fabs(gc[k] - rc[k]) > 1e-11) {
362 std::printf(" (%2d,%2d,%2d) np %.17g ref %.17g d %.3e\n", (int)(k % NX),
363 (int)((k / NX) % NY), (int)(k / (NX * NY)), gc[k], rc[k], gc[k] - rc[k]);
364 ++shown;
365 }
366 }
367 }
368 }
369 delete ref;
370 }
371 Kokkos::finalize();
372 MPI_Finalize();
373 return 0;
374}
void setSolid(const std::vector< double > &sdfInner, bool cutcellPressure)
Host entry point: upload the inner SDF once and delegate.
void setDomainBc(int face, int type, double vx, double vy, double vz)
std::vector< double > getField(const std::string &name)
std::vector< double > getVof()
void setVelocityIterations(int it)
void setVof(const std::vector< double > &c)
void setPressureLevels(int levels)
void setVofBackflow(int f, double value)
inletOutlet backflow colour on outflow face f (default 0 = gas): where the boundary face velocity poi...
void setField(const std::string &name, const std::vector< double > &v)
std::array< int, 3 > blockShape() const
void setVofInflow(int f, double value)
Colour of the fluid entering through inflow face f (0..5 = -x,+x,-y,+y,-z,+z), in [0,...
void setPressurePcg(bool on, int maxit, double rtol)
void setMu(double m)
Dynamic viscosity, in the caller's units.
void setPressureIterations(int it)
void setVofWispEps(double eps)
WO-R2 item 4 — the wisp threshold on the advector's mixed-cell predicate and on the interface Courant...
CCField fieldView(const std::string &name)
void setRho(double r)
Fluid density, in the caller's units.
std::vector< std::string > fieldNames() const
int main()
Definition dbg_scene.cpp:13
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
static constexpr int NY
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 double envd(const char *k, double dflt)
static std::vector< double > packingSdf(int ox, int oy, int oz, int lnx, int lny, int lnz)
static void configurePacking(IbmSolver &s, int ox, int oy, int oz, int lnx, int lny, int lnz, bool perturb=false)
static constexpr int NX
static constexpr std::size_t GCELLS
static constexpr int NZ
peclet::flow::IbmSolver IbmSolver