43#include <Kokkos_Core.hpp>
69 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
82 void init(
int nx,
double hh) {
85 adv.
init(nx, nx, nx, hh,
G);
90std::shared_ptr<VofBlockExchange> serialExchange(
VofBlockSet& set, Patch& p) {
91 auto ex = std::make_shared<VofBlockExchange>();
92 std::vector<VofBox>
rb(1);
93 rb[0].lo[0] =
rb[0].lo[1] =
rb[0].lo[2] = 0;
94 rb[0].hi[0] =
rb[0].hi[1] =
rb[0].hi[2] =
p.n;
96 VofBlockExchange::Patch pp;
97 pp.e =
p.adv.extent();
101 ex->setPatch(pp,
p.adv.faceU(),
p.adv.faceV(),
p.adv.faceW());
113long bitwiseDiffBox(SField a, SField b,
I3 e,
int n,
int g,
const int lo[3],
const int hi[3],
114 double* worst,
double* worstMag) {
115 auto ha = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), a);
116 auto hb = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), b);
118 double w = 0.0, wm = 0.0;
119 for (
int z = 0;
z <
n; ++
z)
120 for (
int y = 0;
y <
n; ++
y)
121 for (
int x = 0;
x <
n; ++
x) {
122 if (x < lo[0] || x >= hi[0] || y < lo[1] || y >= hi[1] || z < lo[2] || z >= hi[2])
124 const double va =
ha(
L3(x + g, y + g, z + g, e)), vb =
hb(
L3(x + g, y + g, z + g, e));
125 if (std::memcmp(&va, &vb,
sizeof(
double)) != 0) {
127 w = std::fmax(w, std::fabs(va - vb));
128 wm = std::fmax(wm, std::fmax(std::fabs(va), std::fabs(vb)));
140 std::vector<double>
v(
static_cast<std::size_t
>(gn) * gn * gn,
141 std::numeric_limits<double>::quiet_NaN());
142 auto hb = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
b.advector().colour());
143 const I3 be =
b.advector().extent();
144 const int g =
b.advector().ghost();
145 for (
int z = 0;
z <
b.box.n(2); ++
z)
146 for (
int y = 0;
y <
b.box.n(1); ++
y)
147 for (
int x = 0;
x <
b.box.n(0); ++
x) {
148 const int gx = ((
b.box.lo[0] +
x) % gn + gn) % gn;
149 const int gy = ((
b.box.lo[1] +
y) % gn + gn) % gn;
150 const int gz = ((
b.box.lo[2] +
z) % gn + gn) % gn;
151 v[
static_cast<std::size_t
>(gx) +
static_cast<std::size_t
>(gy) * gn +
152 static_cast<std::size_t
>(gz) * gn * gn] =
hb(
L3(x + g, y + g, z + g, be));
157std::vector<double> fieldOnGrid(SField f,
I3 e,
int gn,
int g) {
158 std::vector<double>
v(
static_cast<std::size_t
>(gn) * gn * gn, 0.0);
159 auto h = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), f);
160 for (
int z = 0;
z < gn; ++
z)
161 for (
int y = 0;
y < gn; ++
y)
162 for (
int x = 0;
x < gn; ++
x)
163 v[
static_cast<std::size_t
>(x) +
static_cast<std::size_t
>(
y) * gn +
164 static_cast<std::size_t
>(z) * gn * gn] =
h(
L3(x + g, y + g, z + g, e));
169long gridDiff(
const std::vector<double>& a,
const std::vector<double>& b,
double* worst) {
172 for (std::size_t i = 0;
i <
a.size(); ++
i) {
173 if (std::isnan(a[i]) || std::isnan(b[i]))
175 if (std::memcmp(&a[i], &b[i],
sizeof(
double)) != 0) {
177 w = std::fmax(w, std::fabs(a[i] - b[i]));
185long bitwiseDiff(SField a, SField b,
I3 e,
int n,
int g,
double* worst) {
186 auto ha = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), a);
187 auto hb = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), b);
190 for (
int z = 0;
z <
n; ++
z)
191 for (
int y = 0;
y <
n; ++
y)
192 for (
int x = 0;
x <
n; ++
x) {
193 const double va =
ha(
L3(x + g, y + g, z + g, e)), vb =
hb(
L3(x + g, y + g, z + g, e));
194 if (std::memcmp(&va, &vb,
sizeof(
double)) != 0) {
196 w = std::fmax(w, std::fabs(va - vb));
204double sumInner(SField f,
I3 e,
int n,
int g) {
206 Kokkos::parallel_reduce(
209 KOKKOS_LAMBDA(
int x,
int y,
int z,
double& a) {
a +=
f(
L3(x, y, z, e)); },
s);
216 std::printf(
"\n=== P the gather/scatter plan partitions the block box exactly once\n");
218 const I3 gs{gn, gn, gn};
220 std::vector<VofBox>
rb(4);
221 const int cuts[4][6] = {{0, 0, 0, 16, 16, 32},
222 {16, 0, 0, 32, 16, 32},
223 {0, 16, 0, 16, 32, 32},
224 {16, 16, 0, 32, 32, 32}};
225 for (
int r = 0;
r < 4; ++
r)
226 for (
int d = 0;
d < 3; ++
d) {
228 rb[
r].hi[
d] = cuts[
r][3 +
d];
232 std::array<bool, 3> per;
237 std::vector<Case>
cases = {
238 {
VofBox{{4, 4, 4}, {20, 20, 20}}, {
true,
true,
true},
"interior, periodic", 16L * 16 * 16},
239 {
VofBox{{-3, 4, 4}, {13, 20, 20}}, {
true,
true,
true},
"crosses the -x seam", 16L * 16 * 16},
240 {
VofBox{{28, 4, 4}, {44, 20, 20}}, {
true,
true,
true},
"crosses the +x seam", 16L * 16 * 16},
242 {
VofBox{{-3, 4, 4}, {13, 20, 20}},
244 "walled -x, hangs out",
247 {
VofBox{{-3, 4, 4}, {13, 20, 20}},
249 "walled -x, face gather",
252 {
VofBox{{-3, -3, 4}, {35, 13, 20}},
253 {
false,
false,
true},
254 "walled x+y corners, face gather",
257 {
VofBox{{-2, -2, -2}, {34, 34, 34}},
259 "spans the whole grid (clamped)",
262 for (
auto& c :
cases) {
265 std::vector<VofPiece> pieces;
268 std::vector<int> hits(
static_cast<std::size_t
>(
box.cells()), 0);
271 for (
const auto& p : pieces) {
273 for (
int z = 0;
z <
p.g.n(2); ++
z)
274 for (
int y = 0;
y <
p.g.n(1); ++
y)
275 for (
int x = 0;
x <
p.g.n(0); ++
x) {
276 const int lx =
p.loc[0] +
x, ly =
p.loc[1] +
y, lz =
p.loc[2] +
z;
277 if (lx < 0 || ly < 0 || lz < 0 || lx >=
box.n(0) || ly >=
box.n(1) || lz >=
box.n(2)) {
281 ++hits[
static_cast<std::size_t
>(lx) +
static_cast<std::size_t
>(ly) *
box.n(0) +
282 static_cast<std::size_t
>(lz) *
box.n(0) *
box.n(1)];
285 long once = 0, twice = 0;
292 std::printf(
" %-32s pieces %2zu cells %7ld written-once %7ld multiply-written %ld\n",
293 c.what, pieces.size(), total, once, twice);
312void gateOneBubble() {
313 std::printf(
"\n=== G1 one bubble as a block == the global field, BITWISE (LeVeque, T = 3)\n");
315 const double h = 1.0 / gn,
T = 3.0;
316 const long steps = 768;
318 const double cx = 0.35, cy = 0.35, cz = 0.35,
r = 0.15;
325 ref.init(gn, gn, gn, h,
G);
327 const I3 e =
ref.extent();
329 refSphere(ref, rblk, h, cx, cy, cz, r);
333 exact.init(
I3{gn, gn, gn}, {
true,
true,
true}, 0, 1,
h);
334 prod.
init(
I3{gn, gn, gn}, {
true,
true,
true}, 0, 1,
h);
335 exact.bubbleEps = 0.0;
336 serialExchange(exact, patch);
337 serialExchange(prod, patch2);
338 exact.seedSphere(cx, cy, cz, r);
340 exact.scatter(patch.adv.colour());
341 prod.
scatter(patch2.adv.colour());
344 const long d0 = bitwiseDiff(patch.adv.colour(),
ref.colour(), e, gn,
G, &worst);
345 std::printf(
" seed: bitwise diffs union vs global field %ld\n", d0);
348 const double v0 = sumInner(
ref.colour(), e, gn,
G);
349 long nRecentre = 0, nRecentreP = 0, maxCells = 0, maxCellsP = 0;
351 double afterWorst = 0.0;
352 long unionDiffs = 0, unionOther = 0;
353 double unionWorst = 0.0, prodWorst = 0.0;
355 for (
long s = 0;
s <
steps; ++
s) {
356 const double phase = std::cos(M_PI * (s + 0.5) * dt / T);
365 Kokkos::deep_copy(
ref.faceU(), patch.adv.faceU());
366 Kokkos::deep_copy(
ref.faceV(), patch.adv.faceV());
367 Kokkos::deep_copy(
ref.faceW(), patch.adv.faceW());
368 Kokkos::deep_copy(patch2.adv.faceU(), patch.adv.faceU());
369 Kokkos::deep_copy(patch2.adv.faceV(), patch.adv.faceV());
370 Kokkos::deep_copy(patch2.adv.faceW(), patch.adv.faceW());
372 exact.advect(dt, patch.adv.colour());
373 prod.
advect(dt, patch2.adv.colour());
374 nRecentre +=
exact.blocks()[0].stats().recentred ? 1 : 0;
375 nRecentreP += prod.
blocks()[0].stats().recentred ? 1 : 0;
376 maxCells = std::max(maxCells,
exact.blocks()[0].stats().cells);
377 maxCellsP = std::max(maxCellsP, prod.
blocks()[0].stats().cells);
379 const std::vector<double> gr = fieldOnGrid(
ref.colour(), e, gn,
G);
381 const std::vector<double> ge = blockOnGrid(
exact.blocks()[0], gn);
382 const long dBox = gridDiff(ge, gr, &worst);
390 if (dBox != 0 && firstBad0 < 0)
392 afterWorst = std::fmax(afterWorst, worst);
395 const long dp = gridDiff(blockOnGrid(prod.
blocks()[0], gn), gr, &wp);
396 if (dp != 0 && prodFirst < 0)
398 prodWorst = std::fmax(prodWorst, wp);
402 const std::vector<double> gu = fieldOnGrid(patch.adv.colour(), e, gn,
G);
404 const long dUni = gridDiff(gu, gr, &wu);
406 for (std::size_t i = 0;
i < gu.size(); ++
i)
407 if (std::memcmp(&gu[i], &gr[i],
sizeof(
double)) != 0 && !(gr[i] < 0.0 && gu[i] == 0.0))
409 unionDiffs = std::max(unionDiffs, dUni);
410 unionWorst = std::fmax(unionWorst, wu);
411 unionOther = std::max(unionOther, other);
413 const double v1 = sumInner(patch.adv.colour(), e, gn,
G);
414 const double v2 = sumInner(patch2.adv.colour(), e, gn,
G);
415 const double vr = sumInner(
ref.colour(), e, gn,
G);
416 const auto&
st =
exact.blocks()[0].stats();
417 const auto&
sp = prod.
blocks()[0].stats();
418 std::printf(
" eps = 0 : bitwise horizon %ld steps; after it max|d| %.3e over %ld steps\n",
419 firstBad0 < 0 ? steps : firstBad0, afterWorst,
steps);
421 " %ld re-centrings, largest box %ld cells (%.1f%% of the grid) -- the WAKE\n",
422 nRecentre, maxCells, 100.0 * maxCells / (gn * gn * gn));
424 " eps = 1e-12 : bitwise horizon %ld steps; max|d| over the run %.3e, colour dropped %.3e\n",
425 prodFirst < 0 ? steps : prodFirst, prodWorst,
sp.discarded);
426 std::printf(
" %ld re-centrings, largest box %ld cells (%.1f%% of the grid)\n",
427 nRecentreP, maxCellsP, 100.0 * maxCellsP / (gn * gn * gn));
429 " union vs the global field: worst-case %ld cells differ (max|d| %.3e); of those %ld\n"
430 " are NOT the UNPACK_MAX clip of a negative WY residue\n",
431 unionDiffs, unionWorst, unionOther);
432 std::printf(
" volume: union(eps=0) %.17g union(prod) %.17g global %.17g drift %.3e / %.3e\n",
433 v1, v2, vr, std::fabs(v1 - v0) / v0, std::fabs(v2 - v0) / v0);
434 std::printf(
" block stats: V %.6f centroid (%.4f, %.4f, %.4f) moments (%.3e, %.3e, %.3e)\n",
435 st.volume,
st.centroid[0],
st.centroid[1],
st.centroid[2],
st.moment[0],
st.moment[1],
437 CHECK(firstBad0 != 0);
438 CHECK(afterWorst < 1e-15);
439 CHECK(nRecentre > 0);
440 CHECK(nRecentreP > 0);
441 CHECK(unionOther == 0);
442 CHECK(prodWorst < 1e-15);
443 CHECK(std::fabs(
sp.discarded) < 1e-15);
444 CHECK(std::fabs(v1 - v0) / v0 < 1e-13);
445 CHECK(std::fabs(v2 - v0) / v0 < 1e-13);
459KOKKOS_INLINE_FUNCTION
double cellPsi(
double x,
double y,
double A,
double phase,
double x0) {
460 const double PI = 3.14159265358979323846;
461 return -(
A / (2.0 * PI)) * Kokkos::sin(2.0 * PI * (x - x0)) * Kokkos::sin(2.0 * PI * y) * phase;
466 const double invh = 1.0 /
h;
468 b, KOKKOS_LAMBDA(
long i,
int gx,
int gy,
int) {
469 const double xn = gx *
h, yn = gy *
h;
470 const double xp = (gx + 1) * h, yp = (gy + 1) *
h;
471 u(i) = (cellPsi(xp, yp, A, phase, x0) - cellPsi(xp, yn, A, phase, x0)) * invh;
472 v(i) = -(cellPsi(xp, yp, A, phase, x0) - cellPsi(xn, yp, A, phase, x0)) * invh;
477void gateTwoBubbles() {
479 "\n=== G2 two bubbles: overlap without coalescence, and the reversal recovers both\n");
481 const double h = 1.0 / gn;
482 const double A = 1.0,
T = 0.9;
483 const long steps = 216;
488 const double x0 = 0.5 *
h, xatt =
x0 + 0.5;
489 const double r = 0.10, y0 = 0.5, z0 = 0.5;
490 const double x1 = xatt - 0.1208, x2 = xatt + 0.1208;
494 const I3 e = patch.adv.extent();
497 set.
init(
I3{gn, gn, gn}, {
true,
true,
true}, 0, 1,
h);
498 serialExchange(set, patch);
501 set.
scatter(patch.adv.colour());
506 ctl.
init(gn, gn, gn, h,
G);
509 Kokkos::deep_copy(ctl.
colour(), c0);
512 const double vb0[2] = {set.
blocks()[0].stats().volume, set.
blocks()[1].stats().volume};
513 double drift[2] = {0.0, 0.0};
515 double unionDeficitMax = 0.0;
516 for (
long s = 0;
s <
steps; ++
s) {
517 const double phase = std::cos(M_PI * (s + 0.5) * dt / T);
518 fillCellular(patch.adv, patch.blk, h, A, phase, x0);
522 Kokkos::deep_copy(ctl.
faceU(), patch.adv.faceU());
523 Kokkos::deep_copy(ctl.
faceV(), patch.adv.faceV());
524 Kokkos::deep_copy(ctl.
faceW(), patch.adv.faceW());
526 set.
advect(dt, patch.adv.colour());
527 for (
int b = 0;
b < 2; ++
b)
528 drift[b] = std::fmax(drift[b], std::fabs(set.
blocks()[b].stats().volume - vb0[b]) / vb0[b]);
534 auto h0 = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
535 set.
blocks()[0].advector().colour());
536 auto h1 = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
537 set.
blocks()[1].advector().colour());
538 const I3 e0 = set.
blocks()[0].advector().extent(), e1 = set.
blocks()[1].advector().extent();
540 for (
int z = ov.
lo[2];
z < ov.
hi[2]; ++
z)
541 for (
int y = ov.
lo[1];
y < ov.
hi[1]; ++
y)
542 for (
int x = ov.
lo[0];
x < ov.
hi[0]; ++
x) {
543 const double a = h0(
L3(x - b0.
lo[0] +
G, y - b0.
lo[1] +
G, z - b0.
lo[2] +
G, e0));
544 const double bb = h1(
L3(x - b1.
lo[0] +
G, y - b1.
lo[1] +
G, z - b1.
lo[2] +
G, e1));
545 if (a > 1e-12 && bb > 1e-12)
548 overlapMax = std::max(overlapMax, cnt);
551 const double su = sumInner(patch.adv.colour(), e, gn,
G);
552 const double sm = set.
blocks()[0].stats().volume + set.
blocks()[1].stats().volume;
553 unionDeficitMax = std::fmax(unionDeficitMax, sm - su);
559 auto hu = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), patch.adv.colour());
560 auto hc = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), ctl.
colour());
561 const int mx =
static_cast<int>(xatt * gn), my =
static_cast<int>(y0 * gn),
562 mz =
static_cast<int>(z0 * gn);
563 const double neckB = hu(
L3(mx +
G, my +
G, mz +
G, e));
564 const double neckC = hc(
L3(mx +
G, my +
G, mz +
G, e));
565 const double v0 = sumInner(c0, e, gn,
G);
566 const double vB = sumInner(patch.adv.colour(), e, gn,
G);
567 const double vC = sumInner(ctl.
colour(), e, gn,
G);
568 std::printf(
" %ld steps, CFL %.3f: max cells with BOTH markers liquid = %ld\n", steps,
569 A * dt / h, overlapMax);
570 std::printf(
" max union DEFICIT (V_0 + V_1 - V_union) = %.4f cells of shared liquid\n",
572 std::printf(
" per-marker volume drift: block0 %.3e block1 %.3e\n", drift[0], drift[1]);
573 std::printf(
" union volume drift %.3e control volume drift %.3e\n", std::fabs(vB - v0) / v0,
574 std::fabs(vC - v0) / v0);
576 " recovery L1 vs the initial field: BLOCKS %.4e single-field CONTROL %.4e"
578 l1blk, l1ctl, l1ctl / l1blk);
579 std::printf(
" neck cell (%d,%d,%d) at the end: blocks %.3e control %.3e\n", mx, my, mz, neckB,
581 CHECK(overlapMax > 0);
582 CHECK(unionDeficitMax > 0.0);
583 CHECK(drift[0] < 1e-14);
584 CHECK(drift[1] < 1e-14);
585 CHECK(l1ctl > l1blk);
595 std::printf(
"\n=== G3 re-centring: a block that MOVES is bitwise a block that never has to\n");
597 const double h = 1.0 / gn;
598 const double dt = 0.2 *
h;
599 const long steps = 100;
600 const double r = 5.0 *
h;
601 const double epsList[2] = {0.0, 1
e-12};
603 for (
int k = 0;
k < 2; ++
k) {
611 moving.
init(
I3{gn, gn, gn}, {
true,
true,
true}, 0, 1,
h);
612 fixed.init(
I3{gn, gn, gn}, {
true,
true,
true}, 0, 1,
h);
614 fixed.bubbleEps = epsList[
k];
615 fixed.allowShrink =
false;
616 serialExchange(moving, pa);
617 serialExchange(fixed, pb);
618 moving.
seedSphere(10.5 * h, 24.5 * h, 24.5 * h, r);
619 fixed.seedSphere(10.5 * h, 24.5 * h, 24.5 * h, r, 4, 30);
620 moving.
scatter(pa.adv.colour());
621 fixed.scatter(pb.adv.colour());
623 const double v0 = moving.
blocks()[0].stats().volume;
624 const int lo0 = moving.
blocks()[0].box.lo[0];
625 long nMove = 0, nFix = 0, firstBad = -1;
627 for (
long s = 0;
s <
steps; ++
s) {
628 moving.
advect(dt, pa.adv.colour());
629 fixed.advect(dt, pb.adv.colour());
630 nMove += moving.
blocks()[0].stats().recentred ? 1 : 0;
631 nFix +=
fixed.blocks()[0].stats().recentred ? 1 : 0;
634 gridDiff(blockOnGrid(moving.
blocks()[0], gn), blockOnGrid(
fixed.blocks()[0], gn), &w);
635 worst = std::fmax(worst, w);
636 if (d != 0 && firstBad < 0)
639 const auto& sm = moving.
blocks()[0].stats();
640 const auto&
sf =
fixed.blocks()[0].stats();
641 std::printf(
" -- bubbleEps = %.0e\n", epsList[k]);
643 " moving: box [%d,%d)x[%d,%d)x[%d,%d) lo_x %d -> %d %ld re-centrings, %ld cells\n",
644 sm.lo[0], sm.hi[0], sm.lo[1], sm.hi[1], sm.lo[2], sm.hi[2], lo0, sm.lo[0], nMove, sm.cells);
645 std::printf(
" fixed : box [%d,%d)x... %ld re-centrings, %ld cells\n",
sf.lo[0],
sf.hi[0],
647 std::printf(
" first differing step %ld (-1 = none) max|d| over the run %.3e\n", firstBad,
650 " volume %.17g -> %.17g drift %.3e dropped %.3e centroid x %.5f (exact %.5f)\n", v0,
651 sm.volume, std::fabs(sm.volume - v0) / v0, sm.discarded, sm.centroid[0],
652 (10.5 + steps * 0.2) * h);
655 CHECK(std::fabs(sm.volume - v0) / v0 < 1e-14);
656 CHECK(std::fabs(sm.centroid[0] - (10.5 + steps * 0.2) * h) < 0.5 * h);
660 CHECK(worst < 1e-15);
673void gateAssignment() {
674 std::printf(
"\n=== W1a master assignment: round robin vs LPT vs weighted ORB (64 bubbles)\n");
678 const double h = 1.0 / gn;
680 set.
init(
I3{gn, gn, gn}, {
true,
true,
true}, 0, 1,
h);
683 serialExchange(set, patch);
685 for (
int i = 0;
i < 4; ++
i)
686 for (
int j = 0;
j < 4; ++
j)
687 for (
int m = 0;
m < 4; ++
m, ++
k) {
690 const double r = (2.0 + 7.0 * ((
k % 8) / 7.0)) / gn;
691 set.
seedSphere((i + 0.5) / 4, (
j + 0.5) / 4, (m + 0.5) / 4,
r);
694 std::vector<long>
w(set.
count());
695 long tot = 0, wmin = 1L << 60, wmax = 0;
696 for (std::size_t b = 0;
b < set.
count(); ++
b) {
699 wmin = std::min(wmin, w[b]);
700 wmax = std::max(wmax, w[b]);
702 std::printf(
" block cell counts: min %ld max %ld (ratio %.1fx) total %ld\n", wmin, wmax,
703 double(wmax) / wmin, tot);
704 std::printf(
" %-4s %-14s %-14s %-14s\n",
"np",
"round robin",
"LPT",
"weighted ORB");
705 const int nps[4] = {1, 2, 4, 8};
706 for (
int q = 0;
q < 4; ++
q) {
707 const int np = nps[
q];
709 for (
int mode = 0; mode < 3; ++mode) {
712 m.assign(
w.size(), 0);
713 for (std::size_t b = 0;
b <
w.size(); ++
b)
714 m[b] =
static_cast<int>(b % np);
715 }
else if (mode == 1) {
720 std::vector<long>
load(np, 0);
721 for (std::size_t b = 0;
b <
w.size(); ++
b)
725 mx = std::max(
mx,
v);
726 imb[mode] = double(mx) * np / double(tot);
728 std::printf(
" %-4d %-14.4f %-14.4f %-14.4f\n", np, imb[0], imb[1], imb[2]);
730 CHECK(imb[1] <= imb[0] + 1e-12);
731 CHECK(imb[1] <= 4.0 / 3.0 + 1e-12);
735 for (std::size_t b = 1;
b < set.
count(); ++
b)
736 if (set.
blocks()[b].stats().volume > set.
blocks()[big].stats().volume)
738 const auto&
st = set.
blocks()[big].stats();
739 const double R = std::cbrt(
st.volume * 3.0 / (4.0 * M_PI));
740 const double exact = 4.0 * M_PI *
R *
R;
741 std::printf(
" W1d interface area of marker %zu: V %.6e -> R %.5f (%.2f cells), area %.6e vs "
742 "4 pi R^2 %.6e (%+.2f %%)\n",
743 big,
st.volume, R, R / h,
st.area, exact, 100.0 * (
st.area / exact - 1.0));
744 CHECK(std::fabs(
st.area / exact - 1.0) < 0.05);
751 std::printf(
"\n=== W1b/W1c device-resident packing and the block pool are BITWISE inert\n");
753 const double h = 1.0 / gn,
T = 0.75;
754 const long steps = 360;
760 const Cfg
cfgs[4] = {{
false,
false,
"host, no pool"},
761 {
true,
false,
"device, no pool"},
762 {
false,
true,
"host, pool"},
763 {
true,
true,
"device, pool"}};
764 std::vector<double>
ref;
765 std::vector<double> refVol;
766 double packMs[4] = {0, 0, 0, 0};
767 for (
int q = 0;
q < 4; ++
q) {
771 set.
init(
I3{gn, gn, gn}, {
true,
true,
true}, 0, 1,
h);
772 auto ex = serialExchange(set, patch);
773 ex->deviceStaging =
cfgs[
q].dev;
775 for (
int i = 0;
i < 2; ++
i)
776 for (
int j = 0;
j < 2; ++
j)
777 for (
int m = 0;
m < 2; ++
m)
778 set.
seedSphere(0.25 + 0.5 * i, 0.25 + 0.5 * j, 0.25 + 0.5 * m, 0.09);
779 set.
scatter(patch.adv.colour());
782 for (
long s = 0;
s <
steps; ++
s) {
783 const double phase = std::cos(M_PI * (s + 0.5) * dt / T);
789 const double t0 = timer.seconds();
790 set.
advect(dt, patch.adv.colour());
792 tPack += timer.seconds() -
t0;
794 packMs[
q] = 1e3 * tPack /
steps;
795 const std::vector<double> got = fieldOnGrid(patch.adv.colour(), patch.adv.extent(), gn,
G);
796 std::vector<double>
vol(set.
count());
797 for (std::size_t b = 0;
b < set.
count(); ++
b)
798 vol[b] = set.
blocks()[
b].stats().volume;
804 const long d = gridDiff(got, ref, &worst);
806 for (std::size_t b = 0;
b <
vol.size(); ++
b)
807 dv = std::fmax(dv, std::fabs(vol[b] - refVol[b]));
808 std::printf(
" %-18s vs (host, no pool): %ld cells differ, max|dV| %.3e\n", cfgs[q].name, d,
814 std::printf(
" %-18s block-step %.3f ms pool hits %ld misses %ld\n", cfgs[q].name, packMs[q],
819 std::printf(
" device vs host block step: %.3f ms vs %.3f ms (ratio %.2fx)\n", packMs[1],
820 packMs[0], packMs[0] / packMs[1]);
834 std::printf(
"\n=== C1 batched container stages == per-block stages, BITWISE\n");
836 const double h = 1.0 / gn,
T = 0.75;
837 const long steps = 120;
838 const double dt =
T / 360;
839 for (
int geo = 0;
geo < 2; ++
geo) {
840 const std::array<bool, 3> per = {
true,
geo == 0,
true};
841 std::vector<double>
ref;
842 std::vector<std::vector<double>> refBlk;
843 std::vector<peclet::flow::vof::VofBlockStats> refSt;
844 std::vector<double>
refF[3];
846 for (
int q = 0;
q < 2; ++
q) {
850 set.
init(
I3{gn, gn, gn}, per, 0, 1,
h);
851 serialExchange(set, patch);
859 const long plen =
static_cast<long>(patch.adv.extent().x) * patch.adv.extent().y *
860 patch.adv.extent().z;
861 for (
int c = 0;
c < 3; ++
c)
862 force[c] =
SField(
"gate_c1_force", plen);
865 const double y0 =
geo == 0 ? 0.25 : 0.12,
dy =
geo == 0 ? 0.5 : 0.76;
866 for (
int i = 0;
i < 2; ++
i)
867 for (
int j = 0;
j < 2; ++
j)
868 for (
int m = 0;
m < 2; ++
m)
869 set.
seedSphere(0.25 + 0.5 * i, y0 + dy * j, 0.25 + 0.5 * m, 0.09);
870 set.
scatter(patch.adv.colour());
872 for (
const auto& b : set.
blocks())
873 clamped += (
b.
box.
lo[1] == 0 ||
b.
box.hi[1] == gn) ? 1 : 0;
877 for (
long s = 0;
s <
steps; ++
s) {
878 const double phase = std::cos(M_PI * (s + 0.5) * dt / T);
884 set.
advect(dt, patch.adv.colour());
886 for (
const auto& st : set.statsAll())
887 recentred +=
st.recentred ? 1 : 0;
889 std::vector<double> fgot[3];
890 for (
int c = 0;
c < 3; ++
c)
891 fgot[c] = fieldOnGrid(force[c], patch.adv.extent(), gn,
G);
893 const std::vector<double> got = fieldOnGrid(patch.adv.colour(), patch.adv.extent(), gn,
G);
894 std::vector<std::vector<double>> blk;
895 for (std::size_t b = 0;
b < set.
count(); ++
b)
902 for (
int c = 0;
c < 3; ++
c)
905 std::printf(
" %s: %zu blocks (%ld clamped at a wall), %ld re-centrings over %ld steps\n",
906 geo == 0 ?
"periodic" :
"y walls", set.count(), clamped, recentred,
steps);
907 CHECK(recentred > 0);
911 const long d = gridDiff(got, ref, &worst);
913 double c2worst = 0.0;
918 auto rel = [](
const double*
x,
const double*
y,
int m) {
919 double dn = 0.0, yn = 0.0;
920 for (
int k = 0;
k <
m; ++
k) {
921 dn = std::fmax(dn, std::fabs(x[k] - y[k]));
922 yn = std::fmax(yn, std::fabs(y[k]));
924 return yn > 0.0 ? dn / yn : dn;
926 for (std::size_t b = 0;
b < blk.size(); ++
b) {
927 db += (blk[
b].size() == refBlk[
b].size() &&
928 std::memcmp(blk[b].data(), refBlk[b].data(), blk[b].size() *
sizeof(
double)) == 0)
931 const auto &
x =
st[
b], &
y = refSt[
b];
932 c2worst = std::fmax(c2worst,
rel(&
x.volume, &
y.volume, 1));
933 c2worst = std::fmax(c2worst,
rel(&
x.area, &
y.area, 1));
934 c2worst = std::fmax(c2worst,
rel(
x.centroid,
y.centroid, 3));
936 double dv = 0.0, cm = 0.0;
937 for (
int k = 0;
k < 3; ++
k) {
938 dv = std::fmax(dv, std::fabs(
x.velocity[k] -
y.velocity[k]));
939 cm = std::fmax(cm, std::fabs(
y.centroid[k]));
941 c2vel = std::fmax(c2vel, cm > 0.0 ? dv * dt / (2.0 * cm) :
dv);
943 c2worst = std::fmax(c2worst,
rel(
x.moment,
y.moment, 6));
944 bool same =
x.discarded ==
y.discarded &&
945 x.debrisCells ==
y.debrisCells &&
x.debrisVolume ==
y.debrisVolume &&
946 x.debrisReturned ==
y.debrisReturned &&
x.debrisLost ==
y.debrisLost &&
947 x.debrisUnresolved ==
y.debrisUnresolved &&
x.fullAxis ==
y.fullAxis &&
948 x.residueReturned ==
y.residueReturned &&
x.recentred ==
y.recentred &&
950 for (
int k = 0;
k < 3; ++
k)
951 same =
same &&
x.lo[k] ==
y.lo[k] &&
x.hi[k] ==
y.hi[k];
956 for (
int c = 0;
c < 3; ++
c) {
957 df += (std::memcmp(fgot[c].data(), refF[c].data(), fgot[c].size() *
sizeof(
double)) == 0)
960 for (
double v : fgot[
c])
961 fmax = std::fmax(fmax, std::fabs(
v));
963 const bool sameCs =
cs.interfacial == refCs.interfacial &&
cs.hf == refCs.hf &&
964 cs.hfMixed == refCs.hfMixed &&
cs.hfFit == refCs.hfFit &&
965 cs.pv == refCs.pv &&
cs.pvReduced == refCs.pvReduced &&
966 cs.noEstimate == refCs.noEstimate &&
cs.clipped == refCs.clipped;
967 std::printf(
" %s, per-block vs batched: union %ld cells differ, block colours %ld, "
968 "stats %ld (C2 measured stats rel %.2e, velocity as a centroid error %.2e), CSF force fields %ld "
969 "(max|f| %.3e), curvature census %s (interfacial %ld, pv %ld)\n",
970 geo == 0 ?
"periodic" :
"y walls",
d,
db, ds, c2worst, c2vel, df, fmax,
971 sameCs ?
"same" :
"DIFFERS",
cs.interfacial,
cs.pv);
979 CHECK(c2worst <= 1e-12);
980 CHECK(c2vel <= 1e-12);
990double sphereFrac(
double cx,
double cy,
double cz,
double R,
int x,
int y,
int z) {
993 for (
int k = 0;
k <
NS; ++
k)
994 for (
int j = 0;
j <
NS; ++
j)
995 for (
int i = 0;
i <
NS; ++
i) {
996 const double px =
x + (
i + 0.5) / NS - cx, py = y + (j + 0.5) /
NS - cy,
997 pz =
z + (
k + 0.5) / NS - cz;
998 in += (px * px + py * py + pz * pz <
R *
R) ? 1 : 0;
1000 return static_cast<double>(in) / (NS * NS * NS);
1004 static constexpr int NX = 48,
NY = 32,
NZ = 32;
1005 static constexpr double R = 5.0,
SIGMA = 320.0,
RHO_L = 1.0,
RHO_G = 0.1,
MU = 0.5333;
1007 static constexpr double AX = 16.3, BX = 32.3, CY = 16.2, CZ = 16.1;
1009 static constexpr int SX = 21, SY0 = 15, SZ = 16;
1010 static constexpr double SPECK[3] = {1
e-5, 1
e-2, 3
e-3};
1011 std::array<int, 6>
box[2] = {{7, 7, 7, 26, 26, 26}, {18, 7, 7, 41, 26, 26}};
1013 std::vector<double>
colour(
int m,
bool speck)
const {
1014 const std::array<int, 6>&
b =
box[
m];
1015 std::vector<double>
c;
1016 c.reserve(
static_cast<std::size_t
>(b[3] - b[0]) * (b[4] - b[1]) * (b[5] - b[2]));
1017 for (
int z = b[2];
z <
b[5]; ++
z)
1018 for (
int y = b[1];
y <
b[4]; ++
y)
1019 for (
int x = b[0];
x <
b[3]; ++
x) {
1021 if (m == 1 && speck && x == SX && z == SZ && y >= SY0 && y < SY0 + 3)
1027 long localIndex(
int m,
int x,
int y,
int z)
const {
1028 const std::array<int, 6>&
b =
box[
m];
1029 const long nx =
b[3] -
b[0],
ny =
b[4] -
b[1];
1030 return (x - b[0]) + nx * ((
y -
b[1]) + ny *
static_cast<long>(z - b[2]));
1035 s.setPressureGeometry(std::vector<double>(
static_cast<std::size_t
>(NX) * NY * NZ, 10.0));
1036 s.setPressureChebyshev(
true, 800, 1e-12);
1038 s.setVof(std::vector<double>(
static_cast<std::size_t
>(NX) * NY * NZ, 0.0));
1041 s.setSurfaceTension(SIGMA);
1043 s.setVofBlockKappaClip(
false);
1045 s.setVofBlockDebris(
false);
1046 std::vector<std::array<int, 6>> boxes = {
box[0],
box[1]};
1047 std::vector<std::vector<double>>
cols = {
colour(0, speck),
colour(1, speck)};
1048 s.enableVofBlocksFromColours(boxes, cols);
1049 s.enableVofBlockCsf();
1053void gateDebrisSpeck() {
1054 std::printf(
"\n=== D1 seeded debris through the Solver (vof_overlap_design G1)\n");
1056 const double spMax = 1
e-2;
1057 const double bound =
sc.SIGMA * spMax;
1060 sc.build(ref,
false,
true);
1062 sc.build(sp,
true,
true);
1064 sc.build(raw,
true,
false);
1065 const auto kc =
sp.vofBlockKappa(1), kr =
raw.vofBlockKappa(1);
1066 double kmax = 0.0, kraw = 0.0;
1067 for (
int j = 0;
j < 3; ++
j) {
1068 const long q =
sc.localIndex(1,
sc.SX,
sc.SY0 + j,
sc.SZ);
1069 std::printf(
" speck cell y=%d C_B = %.0e kappa_B clipped %.4f (clip off %.4f)\n",
1070 sc.SY0 + j,
sc.SPECK[j], kc[q], kr[q]);
1071 kmax = std::fmax(kmax, std::fabs(kc[q]));
1072 kraw = std::fmax(kraw, std::fabs(kr[q]));
1074 double dF = 0.0, dFraw = 0.0;
1075 for (
int c = 0;
c < 3; ++
c) {
1076 const auto f0 =
ref.getVofBlockForce(c), f1 =
sp.getVofBlockForce(c),
1077 f2 =
raw.getVofBlockForce(c);
1078 for (std::size_t i = 0;
i < f0.size(); ++
i) {
1079 dF = std::fmax(dF, std::fabs(f1[i] - f0[i]));
1080 dFraw = std::fmax(dFraw, std::fabs(f2[i] - f0[i]));
1083 const auto cs =
sp.vofBlockCurvatureStats(), cr =
ref.vofBlockCurvatureStats();
1084 std::printf(
" max|kappa_B| on the speck %.4f (clip off %.4f); max|F - F_ref| %.4e (clip off "
1085 "%.4e), bound sigma*max|dC|/h^2 = %.4e; clipped: speck run %ld, reference %ld\n",
1086 kmax, kraw, dF, dFraw, bound,
cs.clipped, cr.clipped);
1089 CHECK(cr.clipped == 0);
1091 const auto ss =
sp.vofBlockStats(), rs =
ref.vofBlockStats();
1092 std::printf(
" debris census before any step: speck run B %ld, reference %ld/%ld\n",
1093 ss[1].debrisCells, rs[0].debrisCells, rs[1].debrisCells);
1097 sc.build(off,
true,
true,
false);
1098 const double vB0 =
sp.vofBlockStats()[1].volume;
1099 for (
auto* s : {&
ref, &
sp, &
off}) {
1100 s->setDt(0.25 *
s->capillaryDt());
1103 const auto ss1 =
sp.vofBlockStats(), rs1 =
ref.vofBlockStats(), os1 =
off.vofBlockStats();
1104 double sumSpeck = 0.0;
1107 const auto cB =
sp.vofBlockColour(1);
1108 const auto& bb = ss1[1];
1109 auto locB = [&](
int x,
int y,
int z) {
1110 const long nx = bb.hi[0] - bb.lo[0],
ny = bb.hi[1] - bb.lo[1];
1111 return (x - bb.lo[0]) + nx * ((
y - bb.lo[1]) + ny *
static_cast<long>(z - bb.lo[2]));
1113 double speckLeft = 0.0;
1114 for (
int j = 0;
j < 3; ++
j)
1115 speckLeft = std::fmax(speckLeft, std::fabs(cB[locB(
sc.SX,
sc.SY0 + j,
sc.SZ)]));
1118 double residue = 0.0;
1119 for (
int z =
sc.SZ - 3;
z <=
sc.SZ + 3; ++
z)
1120 for (
int y =
sc.SY0 - 3;
y <=
sc.SY0 + 5; ++
y)
1121 for (
int x = bb.lo[0];
x <=
sc.SX + 3; ++
x)
1122 residue += cB[locB(x, y, z)];
1123 const double dVrel = std::fabs(ss1[1].volume - vB0) / vB0;
1124 const auto cA1 =
sp.vofBlockColour(0), cA0 =
off.vofBlockColour(0);
1125 long aDiff = (cA1.size() == cA0.size()) ? 0 : -1;
1126 for (std::size_t i = 0; aDiff >= 0 &&
i < cA1.size(); ++
i)
1127 aDiff += (cA1[i] == cA0[i]) ? 0 : 1;
1128 std::printf(
" after one step: B removed %ld cells / %.17g (painted %.17g, |d| %.3e), lost %.3e, "
1129 "unresolved %ld; speck cells now max %.3e; B volume rel change %.3e; A: %ld cells "
1130 "differ from the removal-OFF run; census-only run reports %ld / %.17g\n",
1131 ss1[1].debrisCells, ss1[1].debrisVolume, sumSpeck,
1132 std::fabs(ss1[1].debrisVolume - sumSpeck), ss1[1].debrisLost,
1133 ss1[1].debrisUnresolved, speckLeft, dVrel, aDiff, os1[1].debrisCells,
1134 os1[1].debrisVolume);
1136 CHECK(rs1[0].debrisCells == 0 && rs1[1].debrisCells == 0);
1137 CHECK(rs1[0].debrisReturned == 0.0 && rs1[1].debrisReturned == 0.0);
1138 CHECK(
ref.vofBlockCurvatureStats().clipped == 0);
1139 CHECK(ss1[0].debrisCells == 0 && ss1[0].debrisReturned == 0.0);
1140 CHECK(ss1[1].debrisCells >= 3);
1141 CHECK(speckLeft == 0.0);
1145 std::printf(
" account: debris removed %.17g, residue returned %.3e (B, whole box), B left "
1146 "in the speck window %.3e (painted %.17g, painted - removed %.3e)\n",
1147 ss1[1].debrisVolume, ss1[1].residueReturned, residue, sumSpeck,
1148 sumSpeck - ss1[1].debrisVolume);
1149 CHECK(residue == 0.0);
1150 CHECK(ss1[1].residueReturned >= sumSpeck - ss1[1].debrisVolume - 1e-15);
1151 CHECK(std::fabs(ss1[1].debrisVolume - sumSpeck) <= 1e-7);
1152 CHECK(ss1[1].debrisReturned == ss1[1].debrisVolume);
1153 CHECK(ss1[1].debrisLost == 0.0);
1154 CHECK(ss1[1].debrisUnresolved == 0);
1155 CHECK(dVrel <= 1e-12);
1157 CHECK(os1[1].debrisCells == ss1[1].debrisCells && os1[1].debrisReturned == 0.0);
1158 double umR = 0.0, umS = 0.0;
1159 for (
int c = 0;
c < 3; ++
c) {
1160 for (
double v :
ref.getVelocity(
c))
1161 umR = std::fmax(umR, std::fabs(
v));
1162 for (
double v :
sp.getVelocity(
c))
1163 umS = std::fmax(umS, std::fabs(
v));
1165 std::printf(
" max|u| after one step: reference (no speck) %.6e, speck %.6e (difference %.3e)\n",
1166 umR, umS, umS - umR);
1167 CHECK(umS <= umR + 1e-6);
1174KOKKOS_INLINE_FUNCTION
double wallPsi(
double x,
double y,
double A) {
1175 const double PI = 3.14159265358979323846;
1176 return -(
A / (2.0 * PI)) * Kokkos::sin(2.0 * PI * x) * 4.0 *
y * (1.0 -
y);
1180 std::printf(
"\n=== W a marker against the wall y = 0: volume exact, block == global field\n");
1182 const double h = 1.0 / gn,
A = 0.5;
1183 const double dt = 0.25 *
h / (4.0 *
A);
1184 const long steps = 240;
1185 const double cx = 0.5, cy = 0.1, cz = 0.5,
r = 0.2;
1186 const std::array<bool, 3> per{
true,
false,
true};
1187 const I3 gs{gn, gn, gn};
1191 const I3 e = patch.adv.extent();
1193 SField u = patch.adv.faceU(),
v = patch.adv.faceV(),
w = patch.adv.faceW();
1194 const double invh = 1.0 /
h;
1196 patch.blk, KOKKOS_LAMBDA(
long i,
int gx,
int gy,
int) {
1197 const double xn = gx *
h, xp = (gx + 1) * h, yn = gy * h, yp = (gy + 1) *
h;
1203 for (SField f : {
u,
v,
w})
1208 double vWall = 0.0, vIn = 0.0;
1210 auto hv = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), patch.adv.faceV());
1211 for (
int z = 0;
z < gn; ++
z)
1212 for (
int x = 0;
x < gn; ++
x) {
1213 vWall = std::fmax(vWall, std::fabs(hv(
L3(x +
G,
G - 1, z +
G, e))));
1214 vIn = std::fmax(vIn, std::fabs(hv(
L3(x +
G,
G, z +
G, e))));
1219 ref.init(gn, gn, gn, h,
G);
1225 refSphere(ref, rblk, h, cx, cy, cz, r);
1227 Kokkos::deep_copy(
ref.faceU(), patch.adv.faceU());
1228 Kokkos::deep_copy(
ref.faceV(), patch.adv.faceV());
1229 Kokkos::deep_copy(
ref.faceW(), patch.adv.faceW());
1232 set.
init(gs, per, 0, 1, h);
1233 serialExchange(set, patch);
1235 set.
scatter(patch.adv.colour());
1236 const auto& b0 = set.
blocks()[0];
1237 const double v0 = b0.stats().volume;
1238 double wallLayer = 0.0;
1240 const std::vector<double> gb = blockOnGrid(b0, gn);
1241 for (
int z = 0;
z < gn; ++
z)
1242 for (
int x = 0;
x < gn; ++
x) {
1243 const double c = gb[
static_cast<std::size_t
>(
x) +
static_cast<std::size_t
>(z) * gn * gn];
1248 const double vr0 = sumInner(
ref.colour(), e, gn,
G);
1250 double worstRel = 0.0, worstD = 0.0;
1251 long firstDiff = -1;
1252 for (
long s = 0;
s <
steps; ++
s) {
1254 set.
advect(dt, patch.adv.colour());
1255 worstRel = std::fmax(worstRel, std::fabs(set.
blocks()[0].stats().volume / v0 - 1.0));
1257 const long d = gridDiff(blockOnGrid(set.
blocks()[0], gn), fieldOnGrid(
ref.colour(), e, gn,
G),
1259 if (d != 0 && firstDiff < 0)
1261 worstD = std::fmax(worstD, wd);
1263 const double vr1 = sumInner(
ref.colour(), e, gn,
G);
1264 const auto&
st = set.
blocks()[0].stats();
1265 std::printf(
" scene: colour in the wall layer %.3f cells; max|v| on the wall face %.3e, one "
1267 wallLayer, vWall, vIn);
1268 std::printf(
" %ld steps: block max|V/V0-1| %.3e (discarded %.3e, box y [%d, %d)); global field "
1270 steps, worstRel,
st.discarded, set.
blocks()[0].box.lo[1], set.
blocks()[0].box.hi[1],
1271 std::fabs(vr1 / vr0 - 1.0));
1272 std::printf(
" block vs global field: bitwise horizon %ld steps, max|d| %.3e\n",
1273 firstDiff < 0 ? steps : firstDiff, worstD);
1274 CHECK(wallLayer > 1.0);
1275 CHECK(vWall == 0.0);
1278 CHECK(worstRel <= 1e-12);
1279 CHECK(std::fabs(vr1 / vr0 - 1.0) <= 1e-12);
1280 CHECK(worstD < 1e-15);
1290 }
catch (
const std::runtime_error& e) {
1291 std::printf(
" refused: %.70s...\n", e.what());
1292 return std::strstr(e.what(),
"not composable") !=
nullptr &&
1293 std::strstr(e.what(),
"block markers") !=
nullptr;
1299 std::printf(
"\n=== M enable_vof_momentum x enable_vof_blocks: refused in either order\n");
1301 const std::vector<std::array<double, 4>> seeds = {{8.0, 8.0, 8.0, 3.0}};
1306 s.enableVofMomentum(0.001, 1.0);
1307 CHECK(s.vofMomentumEnabled());
1309 CHECK(!s.vofBlocksEnabled());
1316 s.enableVofBlocks(seeds);
1317 CHECK(s.vofBlocksEnabled());
1319 CHECK(!s.vofMomentumEnabled());
1324 Kokkos::initialize(argc, argv);
1326 std::printf(
"VoF rung W0 (WO-W0) - per-bubble block container, backend: %s\n", SExec::name());
1337 std::printf(
"\n%s (%d failure%s)\n",
failures ?
"FAILED" :
"PASSED",
failures,
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 ...
VofCurvature::Stats csfCurvatureStats() const
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)
bool batched
C1 (doc/vof_step_performance_design.md §4.6, §5.9): run each stage of the container step for ALL mast...
std::vector< VofBlock > & blocks()
The block table.
void computeCsf(SField fx, SField fy, SField fz)
Per-block curvature cascade + the CSF face force, summed into the caller's three face-force patches.
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...
std::vector< double > blockColourHost(std::size_t idx)
One block's INNER colour, copied to the host in x-fastest order over blocks_[idx]....
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).
bool debrisRemove
Remove the debris and return its volume (§5.3).
void init(I3 gs, std::array< bool, 3 > per, int rank, int size, double h=1.0)
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,...
std::vector< VofBlockStats > statsAll()
Per-bubble census on the master (empty entries on a non-master).
void setWispEps(double eps)
Set wispEps on the set AND on every live advector (pooled ones take it on reuse).
bool usePool
Rung W1 item (c): recycle the advectors a re-centring retires, keyed by the EXACT extent.
std::size_t count() const
double interfaceEps
Wisp threshold on the interfacial predicate.
double pureEps
Tolerance of the PURITY test that ends a height-function column (hfIsFull/hfIsEmpty),...
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)
void syncGhosts()
Bring the colour ghosts up to date.
std::function< void(SField)> exchange
Refresh EVERY ghost layer of the given cell field (MPI exchange + domain BC fill)....
void advect(double dt, long step)
One Weymouth-Yue step: freeze the dilation flag, then three sweeps in the permutation selected by ste...
SField faceU() const
Face velocity in direction d at the +d face of each cell: uf(i,j,k) sits at (i+1/2,...
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
raw(name, rev, calls=400)
exact(Ds, K, hc, gs, nmax=1)
void vofAssignOrb(const std::vector< long > &w, int size, std::vector< int > &master)
Weighted ORB over a 1-D block space: the blocks are laid out as a 1-D "grid" of N cells in block-id o...
VofBox vofClampBox(VofBox b, I3 gs, const std::array< bool, 3 > &per)
Clamp a block box so it is representable: at most the whole axis on a periodic axis,...
void vofAssignLpt(const std::vector< long > &w, int size, std::vector< int > &master)
Longest-processing-time greedy (Graham 1969): heaviest block first onto the currently least-loaded ra...
void vofBuildPieces(const VofBox &box, I3 gs, const std::array< bool, 3 > &per, const std::vector< VofBox > &rankBox, std::vector< VofPiece > &out, int outside=0)
Build the pieces of one block box against every rank's owned box, in canonical order.
long L3(int x, int y, int z, I3 e)
Kokkos::DefaultExecutionSpace SExec
Kokkos::View< double *, SMem > SField
void fillLeVeque(WyAdvector &a, Block b, double h, double phase)
double l1Diff(SField a, SField b, I3 e, I3 n, int g)
sum |a - b| over the inner region (cell-fraction units).
void forEachExtended(Block b, F fn)
Visit every cell of the extended block with (local linear index, global cell index).
SField copyOf(SField src, const char *name)
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).
void initSphere(SField c, Block b, double h, double cx, double cy, double cz, double r, int levels=4)
Sphere of fluid (C = 1 inside), exact to ~5e-7 relative volume at R/h = 16 via the octree helper in p...
void clampFill(SField f, Block b, I3 gs, bool px, bool py, bool pz)
Zero-gradient (Neumann) fill of the ghost cells that lie OUTSIDE the global domain on a non-periodic ...
Block blockOf(const WyAdvector &a, I3 origin)
Half-open global cell box [lo, hi).
static VofBox intersect(const VofBox &a, const VofBox &b)
Intersection in global coordinates (may be empty).
Per-call census of the cascade over this block's inner region (LOCAL; a distributed caller sums them ...
One contiguous transfer: the global cells g of a block that rank r owns, and where they sit in the bl...
Where this block sits in the global grid: o is the global index of inner cell (0,0,...
I3 e
extended (inner + 2g) counts
static constexpr double F
static bool same(double a, double b)
void gateMomentumRefused()
bool refusesMomentumBlocks(F &&f)
Runs f, returns true iff it threw a std::runtime_error naming the momentum/block conflict.
static std::vector< double > colour(int m)
static constexpr double SPECK[3]
static double sphereFrac(double cx, int x, int y, int z)
static double wallPsi(int x, int y)
psi on the z-edge (x, y): zero on both walls, periodic in x with an EXACT wrap (x mod NX).