flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
test_vof_blocks_overlap.cpp
Go to the documentation of this file.
1// flow — the overlap-robustness rules of the block VoF container (doc/vof_overlap_design.md §5,
2// §11-§13), gated one mechanism at a time (review doc/vof_overlap_review.md findings 1-3).
3//
4// K the admissibility clip is LIVE ON THE BLOCK PATH: a detached cell of marker B (C = 1e-6) two
5// cells outside B's resolved surface gets |kappa| = 4.4 from the block cascade without the
6// clip and <= 1 with it (Solver, enable_vof_blocks_from_colors + enable_vof_block_csf); the
7// face force on the speck's own faces moves by at most sigma |dC| / Delta^2.
8// P every tunable of the block cascade prototype (VofBlockSet::curvProto) and the advector's
9// wispEps / metric reach every block: at seeding, after a re-centring re-allocates the block,
10// and after setWispEps / setKappaMax on live blocks. (Finding 1: pureEps once did not.)
11// W §12.3: a marker straddling the periodic x seam whose box must grow to the whole axis is
12// re-centred WITH the wrap -- fullAxis > 0, nothing discarded, volume exact, and (wispEps 0)
13// the union equal to a global-field WyAdvector to round-off.
14// R §13: 1000 kinematic steps of a translating marker at block wispEps 1e-8 conserve the volume
15// to 1e-14 relative with nothing discarded (the residue is returned), where the pre-§13
16// container leaked ~1e-10.
17#include <array>
18#include <cmath>
19#include <cstdio>
20#include <Kokkos_Core.hpp>
21#include <memory>
22#include <vector>
23
24#include "flow_ibm.hpp"
27#include "vof_advect_scenes.hpp"
28
37
38namespace {
39
40int failures = 0;
41#define CHECK(cond) \
42 do { \
43 if (!(cond)) { \
44 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
45 ++failures; \
46 } \
47 } while (0)
48
49constexpr int G = 3;
50
51double sumInner(SField f, I3 e, I3 n) {
52 auto h = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), f);
53 double s = 0.0;
54 for (int z = 0; z < n.z; ++z)
55 for (int y = 0; y < n.y; ++y)
56 for (int x = 0; x < n.x; ++x)
57 s += h(L3(x + G, y + G, z + G, e));
58 return s;
59}
60
62void serialExchange(VofBlockSet& set, WyAdvector& pa, I3 gs) {
63 auto ex = std::make_shared<VofBlockExchange>();
64 std::vector<VofBox> rb(1);
65 rb[0].lo[0] = rb[0].lo[1] = rb[0].lo[2] = 0;
66 rb[0].hi[0] = gs.x;
67 rb[0].hi[1] = gs.y;
68 rb[0].hi[2] = gs.z;
69 ex->init(gs, set.periodic(), rb, 0);
70 VofBlockExchange::Patch pp;
71 pp.e = pa.extent();
72 pp.n = pa.inner();
73 pp.o = I3{0, 0, 0};
74 pp.g = G;
75 ex->setPatch(pp, pa.faceU(), pa.faceV(), pa.faceW());
76 set.setExchange(ex);
77}
78
79// ------------------------------------------------------------------------------------------ K
80double sphereFrac(double cx, double cy, double cz, double R, int x, int y, int z) {
81 const int NS = 8;
82 int in = 0;
83 for (int k = 0; k < NS; ++k)
84 for (int j = 0; j < NS; ++j)
85 for (int i = 0; i < NS; ++i) {
86 const double px = x + (i + 0.5) / NS - cx, py = y + (j + 0.5) / NS - cy,
87 pz = z + (k + 0.5) / NS - cz;
88 in += (px * px + py * py + pz * pz < R * R) ? 1 : 0;
89 }
90 return static_cast<double>(in) / (NS * NS * NS);
91}
92
93void gateClipOnBlockPath() {
94 std::printf(
95 "\n=== K the admissibility clip is live on the BLOCK path (a speck at B's fringe)\n");
96 constexpr int NX = 48, NY = 32, NZ = 32;
97 constexpr double R = 5.0, SIGMA = 320.0, BX = 32.3, CY = 16.2, CZ = 16.1;
98 const std::array<int, 6> box = {18, 7, 7, 41, 26, 26};
99 // (25, 15, 16): 1.84 cells outside B's surface -- measured |kappa| 4.41 unclipped
100 constexpr int SX = 25, SY = 15, SZ = 16;
101 const long nx = box[3] - box[0], ny = box[4] - box[1];
102 auto loc = [&](int x, int y, int z) {
103 return (x - box[0]) + nx * ((y - box[1]) + ny * static_cast<long>(z - box[2]));
104 };
105 auto build = [&](peclet::flow::IbmSolver& s, bool speck, bool clip) {
106 s.setRho(1.0);
107 s.setMu(0.5);
108 s.setPressureGeometry(std::vector<double>(static_cast<std::size_t>(NX) * NY * NZ, 10.0));
109 s.enableVof();
110 s.setVof(std::vector<double>(static_cast<std::size_t>(NX) * NY * NZ, 0.0));
111 s.setSurfaceTension(SIGMA);
112 if (!clip)
113 s.setVofBlockKappaClip(false);
114 std::vector<double> c;
115 for (int z = box[2]; z < box[5]; ++z)
116 for (int y = box[1]; y < box[4]; ++y)
117 for (int x = box[0]; x < box[3]; ++x)
118 c.push_back(sphereFrac(BX, CY, CZ, R, x, y, z));
119 if (speck) {
120 CHECK(c[loc(SX, SY, SZ)] == 0.0); // the cell really is detached
121 c[loc(SX, SY, SZ)] = 1e-6;
122 }
123 s.enableVofBlocksFromColours({box}, {c});
124 s.enableVofBlockCsf(); // computes the block curvature and the CSF force
125 };
127 build(ref, false, true);
128 build(off, true, false);
129 build(on, true, true);
130 const double kOff = off.vofBlockKappa(0)[loc(SX, SY, SZ)];
131 const double kOn = on.vofBlockKappa(0)[loc(SX, SY, SZ)];
132 const long clipOn = on.vofBlockCurvatureStats().clipped,
133 clipOff = off.vofBlockCurvatureStats().clipped,
134 clipRef = ref.vofBlockCurvatureStats().clipped;
135 // face force on the speck's six faces (low face of (SX,SY,SZ) and of its +1 neighbours)
136 const long gi = SX + static_cast<long>(NX) * (SY + static_cast<long>(NY) * SZ);
137 const long st[3] = {1, NX, static_cast<long>(NX) * NY};
138 double dFon = 0.0, dFoff = 0.0, dFallOn = 0.0;
139 for (int c = 0; c < 3; ++c) {
140 const auto fr = ref.getVofBlockForce(c), fo = off.getVofBlockForce(c),
141 fn = on.getVofBlockForce(c);
142 for (long f : {gi, gi + st[c]}) {
143 dFon = std::fmax(dFon, std::fabs(fn[f] - fr[f]));
144 dFoff = std::fmax(dFoff, std::fabs(fo[f] - fr[f]));
145 }
146 for (std::size_t i = 0; i < fr.size(); ++i)
147 dFallOn = std::fmax(dFallOn, std::fabs(fn[i] - fr[i]));
148 }
149 const double bound = SIGMA * 1e-6; // sigma max|dC_speck| / Delta^2 on the speck's own faces
150 std::printf(
151 " speck kappa: clip off %.4f, clip on %.4f; clipped: on %ld, off %ld, no-speck %ld\n", kOff,
152 kOn, clipOn, clipOff, clipRef);
153 std::printf(
154 " |F - F_ref| on the speck's faces: clip on %.3e, clip off %.3e (bound %.3e); "
155 "anywhere, clip on: %.3e\n",
156 dFon, dFoff, bound, dFallOn);
157 CHECK(std::fabs(kOff) > 1.0); // the degenerate fit really is unbounded without the clip
158 CHECK(std::fabs(kOn) <= 1.0);
159 CHECK(clipOn >= 1);
160 CHECK(clipOff == 0);
161 CHECK(clipRef == 0); // a resolved marker never fires it
162 CHECK(dFon <= bound * (1.0 + 1e-12));
163}
164
165// ------------------------------------------------------------------------------------------ P
167bool sameTunables(const VofCurvature& a, const VofCurvature& b) {
168 bool ok = true;
169 for (int d = 0; d < 3; ++d)
170 ok = ok && a.metric.h[d] == b.metric.h[d];
171 return ok && a.weightWidth == b.weightWidth && a.pureEps == b.pureEps && a.monoTol == b.monoTol &&
172 a.ptWeightWidth == b.ptWeightWidth && a.cosMin == b.cosMin &&
173 a.interfaceEps == b.interfaceEps && a.debugForceFallback == b.debugForceFallback &&
174 a.debugSingleDirection == b.debugSingleDirection &&
175 a.useMixedHeightFit == b.useMixedHeightFit && a.kappaMax == b.kappaMax &&
176 a.useWorklist == b.useWorklist;
177}
178
179void gatePrototypePropagation() {
180 std::printf("\n=== P the block prototype's tunables reach every block cascade and advector\n");
181 const I3 gs{40, 40, 40};
182 WyAdvector pa;
183 pa.init(gs.x, gs.y, gs.z, 1.0, G);
184 vofscene::Block blk = vofscene::blockOf(pa, I3{0, 0, 0});
185 vofscene::fillUniform(pa, blk, 1.0, 0.0, 0.0);
186 VofBlockSet set;
187 set.init(gs, {true, true, true}, 0, 1, 1.0);
188 serialExchange(set, pa, gs);
189 // every tunable at a NON-default value, so a field the copy forgets is caught
190 const peclet::flow::vof::VofMetric m{{1.0, 1.0, 1.0 + 1e-9}};
191 set.setMetric(m);
192 VofCurvature& p = set.curvProto;
193 p.weightWidth = 2.4;
194 p.monoTol = 2e-6;
195 p.ptWeightWidth = 1.9;
196 p.cosMin = 0.21;
197 p.interfaceEps = 3e-8;
198 p.debugSingleDirection = true;
199 p.debugForceFallback = true; // P computes no curvature; set so a copy that drops it is caught
200 p.useMixedHeightFit = true;
201 p.useWorklist = false;
202 p.kappaMax = 1.7;
203 set.setWispEps(2e-9); // sets the advectors AND curvProto.pureEps
204 const VofCurvature defaults;
205 CHECK(!sameTunables(p, defaults));
206 set.enableCsf(1.0);
207 set.seedSphere(8.3, 20.1, 20.2, 5.0);
208 set.scatter(pa.colour());
209 auto allReached = [&](const char* when) {
210 bool ok = true;
211 for (const auto& b : set.blocks()) {
212 if (!b.mine())
213 continue;
214 const bool c = sameTunables(b.curvature(), set.curvProto);
215 const bool a =
216 b.advector().wispEps == set.wispEps && b.advector().metric.h[2] == set.metric().h[2];
217 std::printf(" %-34s block %ld: cascade tunables %s, advector wispEps/metric %s\n", when,
218 b.id, c ? "match" : "MISMATCH", a ? "match" : "MISMATCH");
219 ok = ok && c && a;
220 }
221 return ok;
222 };
223 CHECK(allReached("after seeding"));
224 long nRe = 0;
225 for (long s = 0; s < 40; ++s) { // 8 cells of translation: several re-centrings
226 set.advect(0.2, pa.colour());
227 nRe += set.blocks()[0].stats().recentred ? 1 : 0;
228 }
229 std::printf(" %ld re-centrings\n", nRe);
230 CHECK(nRe > 0);
231 CHECK(allReached("after re-centring"));
232 set.setWispEps(4e-9);
233 set.setKappaMax(0.9);
234 CHECK(set.curvProto.pureEps == 4e-9 && set.curvProto.kappaMax == 0.9);
235 CHECK(allReached("after setWispEps/setKappaMax"));
236}
237
238// ------------------------------------------------------------------------------------------ W
239void gateWrapRecentre() {
240 std::printf("\n=== W §12.3 lossless periodic-wrap re-centring (a seam-straddling marker)\n");
241 for (double wisp : {0.0, 1e-8}) {
242 const int NX = 20, NY = 40, NZ = 40;
243 const double h = 1.0, R = 5.0, cx = 1.3, cy = 12.3, cz = 20.1, dt = 0.2;
244 WyAdvector pa, ref;
245 pa.init(NX, NY, NZ, h, G);
246 ref.init(NX, NY, NZ, h, G);
247 const I3 e = pa.extent(), n = pa.inner();
248 vofscene::Block blk = vofscene::blockOf(pa, I3{0, 0, 0});
249 vofscene::fillUniform(pa, blk, 0.0, 1.0, 0.0);
250 vofscene::fillUniform(ref, blk, 0.0, 1.0, 0.0);
251 ref.exchange = [e](SField f) { vofscene::periodicFill(f, e, G, true, true, true); };
252 ref.wispEps = wisp;
253 {
254 SField c = ref.colour();
256 blk, KOKKOS_LAMBDA(long i, int gx, int gy, int gz) {
257 c(i) =
258 peclet::flow::vof::sphereCellFraction(cx, cy, cz, R, gx * h, gy * h, gz * h, h, 4) +
259 peclet::flow::vof::sphereCellFraction(cx + NX, cy, cz, R, gx * h, gy * h, gz * h, h,
260 4) +
261 peclet::flow::vof::sphereCellFraction(cx - NX, cy, cz, R, gx * h, gy * h, gz * h, h,
262 4);
263 });
264 ref.syncGhosts();
265 }
266 VofBlockSet set;
267 set.init(I3{NX, NY, NZ}, {true, true, true}, 0, 1, h);
268 set.bubbleEps = 0.0;
269 set.setWispEps(wisp);
270 serialExchange(set, pa, I3{NX, NY, NZ});
271 set.seedSphere(cx, cy, cz, R);
272 set.scatter(pa.colour());
273 const int lo0 = set.blocks()[0].box.lo[0], hi0 = set.blocks()[0].box.hi[0];
274 const double v0 = set.blocks()[0].stats().volume;
275 double worst = 0.0;
276 long nRe = 0;
277 for (long s = 0; s < 60; ++s) {
278 ref.advect(dt, s);
279 set.advect(dt, pa.colour());
280 nRe += set.blocks()[0].stats().recentred ? 1 : 0;
281 auto hu = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), pa.colour());
282 auto hr = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), ref.colour());
283 for (int z = 0; z < NZ; ++z)
284 for (int y = 0; y < NY; ++y)
285 for (int x = 0; x < NX; ++x) {
286 const long i = L3(x + G, y + G, z + G, e);
287 worst = std::fmax(worst, std::fabs(hu(i) - hr(i)));
288 }
289 }
290 const auto& st = set.blocks()[0].stats();
291 const double dv = std::fabs(st.volume - v0) / v0;
292 std::printf(
293 " wispEps %.0e: box x [%d,%d) -> [%d,%d); %ld re-centrings, fullAxis %ld, "
294 "discarded %.3e, volume rel %.3e, max|union - global| %.3e\n",
295 wisp, lo0, hi0, st.lo[0], st.hi[0], nRe, st.fullAxis, st.discarded, dv, worst);
296 CHECK(lo0 < 0); // seeded across the seam
297 CHECK(st.lo[0] == 0 && st.hi[0] == NX);
298 CHECK(st.fullAxis > 0);
299 CHECK(st.discarded == 0.0);
300 CHECK(dv <= 1e-14);
301 if (wisp == 0.0)
302 CHECK(worst <= 1e-14); // lossless: the union IS the global field to round-off
303 (void)n;
304 }
305}
306
307// ------------------------------------------------------------------------------------------ R
308void gateResidueReturn() {
309 std::printf("\n=== R §13 residue return: 1000 kinematic steps, volume exact at wispEps 1e-8\n");
310 const I3 gs{48, 48, 48};
311 const long steps = 1000;
312 double dv[2] = {0, 0}, disc[2] = {0, 0}, res[2] = {0, 0};
313 const double wisps[2] = {0.0, 1e-8};
314 for (int k = 0; k < 2; ++k) {
315 WyAdvector pa;
316 pa.init(gs.x, gs.y, gs.z, 1.0, G);
317 vofscene::Block blk = vofscene::blockOf(pa, I3{0, 0, 0});
318 vofscene::fillUniform(pa, blk, 0.9, 0.6, 0.3);
319 VofBlockSet set;
320 set.init(gs, {true, true, true}, 0, 1, 1.0);
321 set.setWispEps(wisps[k]);
322 serialExchange(set, pa, gs);
323 set.seedSphere(24.2, 24.1, 23.9, 8.0);
324 set.scatter(pa.colour());
325 const double v0 = set.blocks()[0].stats().volume;
326 for (long s = 0; s < steps; ++s)
327 set.advect(0.2, pa.colour());
328 const auto& st = set.blocks()[0].stats();
329 dv[k] = (st.volume - v0) / v0;
330 disc[k] = st.discarded;
331 res[k] = st.residueReturned;
332 std::printf(" wispEps %.0e: volume rel change %+.3e, discarded %.3e, residueReturned %.3e\n",
333 wisps[k], dv[k], disc[k], res[k]);
334 }
335 CHECK(std::fabs(dv[1]) <= 1e-14);
336 CHECK(disc[1] == 0.0);
337 CHECK(res[1] != 0.0); // the pass ran: residue exists and was returned
338 CHECK(res[0] == 0.0); // wispEps 0 = the W0 container verbatim: the pass never runs
339}
340
341} // namespace
342
343// ------------------------------------------------------------------------------------------ M
348 std::printf("\n=== M enable_vof_blocks on an anisotropic box: the blocks carry its metric\n");
349 constexpr int NX = 32, NY = 32, NZ = 32;
350 const std::array<int, 6> box = {8, 8, 8, 24, 24, 24};
351 // cells 1.5 : 1 : 2 -- every axis different, so a dropped or permuted metric is caught
352 peclet::flow::IbmSolver s(NX, NY, NZ, {48.0, 32.0, 64.0}, {0.0, 0.0, 0.0}, {NX, NY, NZ});
353 s.setRho(1.0);
354 s.setMu(0.5);
355 s.setPressureGeometry(std::vector<double>(static_cast<std::size_t>(NX) * NY * NZ, 10.0));
356 s.enableVof();
357 s.setVof(std::vector<double>(static_cast<std::size_t>(NX) * NY * NZ, 0.0));
358 s.setSurfaceTension(1.0);
359 std::vector<double> c;
360 for (int z = box[2]; z < box[5]; ++z)
361 for (int y = box[1]; y < box[4]; ++y)
362 for (int x = box[0]; x < box[3]; ++x)
363 c.push_back(sphereFrac(16.2, 16.1, 15.9, 5.0, x, y, z));
364 s.enableVofBlocksFromColours({box}, {c});
365 s.enableVofBlockCsf();
366 const auto* set = s.vofBlockSet();
367 auto ratios = [](const peclet::flow::vof::VofMetric& m, const char* who) {
368 const double rx = m.h[0] / m.h[1], rz = m.h[2] / m.h[1];
369 std::printf(" %-22s h = (%.4f, %.4f, %.4f), hx/hy %.4f, hz/hy %.4f\n", who, m.h[0], m.h[1],
370 m.h[2], rx, rz);
371 return std::fabs(rx - 1.5) < 1e-12 && std::fabs(rz - 2.0) < 1e-12;
372 };
373 CHECK(set != nullptr);
374 CHECK(ratios(set->metric(), "set"));
375 CHECK(ratios(set->curvProto.metric, "curvature prototype"));
376 for (const auto& b : set->blocks()) {
377 if (!b.mine())
378 continue;
379 CHECK(ratios(b.curvature().metric, "block cascade"));
380 CHECK(ratios(b.advector().metric, "block advector"));
381 }
382}
383
384int main(int argc, char** argv) {
385 Kokkos::initialize(argc, argv);
386 {
387 std::printf("VoF block container: overlap-robustness rules (vof_overlap_design), backend %s\n",
388 peclet::flow::SExec::name());
389 gateClipOnBlockPath();
390 gatePrototypePropagation();
392 gateWrapRecentre();
393 gateResidueReturn();
394 std::printf("\n%s (%d failure%s)\n", failures ? "FAILED" : "PASSED", failures,
395 failures == 1 ? "" : "s");
396 }
397 Kokkos::finalize();
398 return failures ? 1 : 0;
399}
flow — VoF Part III rung W0: the per-bubble VoF BLOCK, a third container over the same L1 kernels (su...
flow — VoF Part III rung W0: the block gather/scatter (VOF_PLAN.md §10 design items 2 and 4).
The gather / scatter / table replication of the block container.
The container: a replicated table of blocks, the per-step orchestration (gather → advect → re-centre ...
void enableCsf(double sigmaValue)
Turn on the block CSF.
double bubbleEps
The colour threshold that defines the BUBBLE EXTENT (and hence the box).
void setExchange(std::shared_ptr< VofBlockExchangeBase > x)
std::vector< VofBlock > & blocks()
The block table.
std::array< bool, 3 > periodic() const
VofCurvature curvProto
The curvature cascade's TUNABLES for every block, copied into each block's own VofCurvature when it i...
void setKappaMax(double km)
Set the curvature admissibility clip (VofCurvature::kappaMax, index units) on the prototype AND on ev...
void seedSphere(double cx, double cy, double cz, double r, int subLevels=4, int extraPad=0)
Seed a spherical bubble (centre and radius in CELL units, global).
void init(I3 gs, std::array< bool, 3 > per, int rank, int size, double h=1.0)
const VofMetric & metric() const
void scatter(SField cLocal)
Union the current colour into cLocal without advecting (used right after seeding).
void advect(double dt, SField cLocal)
One kinematic block step: gather the face velocity, advect every local block, re-centre,...
void setMetric(const VofMetric &g)
The anisotropic cell metric (Phase 3).
void setWispEps(double eps)
Set wispEps on the set AND on every live advector (pooled ones take it on reuse).
double wispEps
The Weymouth-Yue wisp tolerance of every block advector (WyAdvector::wispEps); the solver sets it to ...
Curvature of the colour field on an extended (inner + ghost) block.
double pureEps
Tolerance of the PURITY test that ends a height-function column (hfIsFull/hfIsEmpty),...
double kappaMax
Curvature admissibility clip (doc/vof_overlap_design.md §5.1, §11): after the cascade,...
VofMetric metric
The anisotropic cell metric (Phase 3).
Weymouth-Yue split advection of a colour field on an extended (inner + ghost) block.
SField colour() const
Colour field on the extended block (x-fastest).
void init(int nx, int ny, int nz, double h, int ghost=3)
SField faceU() const
Face velocity in direction d at the +d face of each cell: uf(i,j,k) sits at (i+1/2,...
int main()
Definition dbg_scene.cpp:13
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
float worst
Definition cmp.py:18
x
Definition cmp.py:24
y
Definition cmp.py:24
dict res
Definition disc_bc.py:230
long L3(int x, int y, int z, I3 e)
Kokkos::View< double *, SMem > SField
blocks
Definition prof.py:61
void forEachExtended(Block b, F fn)
Visit every cell of the extended block with (local linear index, global cell index).
void periodicFill(SField f, I3 e, int g, bool px, bool py, bool pz)
Periodic wrap of a single-block extended field (serial reference path; the MPI path uses core's GridH...
void fillUniform(WyAdvector &a, Block b, double ux, double uy, double uz)
Uniform translation. Discrete divergence is EXACTLY zero (identical values differenced).
Block blockOf(const WyAdvector &a, I3 origin)
static constexpr int NY
static constexpr int NX
static constexpr int NZ
Half-open global cell box [lo, hi).
Where this block sits in the global grid: o is the global index of inner cell (0,0,...
static constexpr int G
static constexpr double SIGMA
static constexpr double CY
static constexpr int SZ
static constexpr int SX
static constexpr double BX
static constexpr double CZ
static double sphereFrac(double cx, int x, int y, int z)
void gateSolverMetric()
The Solver hands the anisotropic cell metric to the block set it creates – in the documented order th...
#define CHECK(cond)