20#include <Kokkos_Core.hpp>
44 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
51double sumInner(SField f,
I3 e,
I3 n) {
52 auto h = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), f);
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));
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;
70 VofBlockExchange::Patch pp;
80double sphereFrac(
double cx,
double cy,
double cz,
double R,
int x,
int y,
int z) {
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;
90 return static_cast<double>(in) / (NS * NS * NS);
93void gateClipOnBlockPath() {
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};
100 constexpr int SX = 25, SY = 15,
SZ = 16;
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]));
108 s.setPressureGeometry(std::vector<double>(
static_cast<std::size_t
>(
NX) *
NY *
NZ, 10.0));
110 s.setVof(std::vector<double>(
static_cast<std::size_t
>(
NX) *
NY *
NZ, 0.0));
111 s.setSurfaceTension(
SIGMA);
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)
121 c[loc(
SX, SY,
SZ)] = 1
e-6;
123 s.enableVofBlocksFromColours({
box}, {
c});
124 s.enableVofBlockCsf();
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;
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]));
146 for (std::size_t i = 0;
i < fr.size(); ++
i)
147 dFallOn = std::fmax(dFallOn, std::fabs(fn[i] - fr[i]));
151 " speck kappa: clip off %.4f, clip on %.4f; clipped: on %ld, off %ld, no-speck %ld\n", kOff,
152 kOn, clipOn, clipOff, clipRef);
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);
158 CHECK(std::fabs(kOn) <= 1.0);
162 CHECK(dFon <= bound * (1.0 + 1e-12));
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;
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};
187 set.
init(gs, {
true,
true,
true}, 0, 1, 1.0);
188 serialExchange(set, pa, gs);
190 const peclet::flow::vof::VofMetric
m{{1.0, 1.0, 1.0 + 1
e-9}};
195 p.ptWeightWidth = 1.9;
197 p.interfaceEps = 3
e-8;
198 p.debugSingleDirection =
true;
199 p.debugForceFallback =
true;
200 p.useMixedHeightFit =
true;
201 p.useWorklist =
false;
205 CHECK(!sameTunables(p, defaults));
209 auto allReached = [&](
const char* when) {
211 for (
const auto& b : set.
blocks()) {
214 const bool c = sameTunables(
b.curvature(), set.
curvProto);
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");
223 CHECK(allReached(
"after seeding"));
225 for (
long s = 0;
s < 40; ++
s) {
227 nRe += set.
blocks()[0].stats().recentred ? 1 : 0;
229 std::printf(
" %ld re-centrings\n", nRe);
231 CHECK(allReached(
"after re-centring"));
235 CHECK(allReached(
"after setWispEps/setKappaMax"));
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, 1
e-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;
256 blk, KOKKOS_LAMBDA(
long i,
int gx,
int gy,
int gz) {
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,
261 peclet::flow::vof::sphereCellFraction(cx -
NX, cy, cz, R, gx * h, gy * h, gz * h, h,
270 serialExchange(set, pa,
I3{
NX,
NY,
NZ});
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;
277 for (
long s = 0;
s < 60; ++
s) {
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)));
290 const auto&
st = set.
blocks()[0].stats();
291 const double dv = std::fabs(
st.volume - v0) / v0;
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);
302 CHECK(worst <= 1e-14);
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, 1
e-8};
314 for (
int k = 0;
k < 2; ++
k) {
320 set.
init(gs, {
true,
true,
true}, 0, 1, 1.0);
322 serialExchange(set, pa, gs);
325 const double v0 = set.
blocks()[0].stats().volume;
326 for (
long s = 0;
s <
steps; ++
s)
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]);
335 CHECK(std::fabs(dv[1]) <= 1e-14);
336 CHECK(disc[1] == 0.0);
337 CHECK(res[1] != 0.0);
338 CHECK(res[0] == 0.0);
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};
352 peclet::flow::IbmSolver s(
NX,
NY,
NZ, {48.0, 32.0, 64.0}, {0.0, 0.0, 0.0}, {
NX,
NY,
NZ});
355 s.setPressureGeometry(std::vector<double>(
static_cast<std::size_t
>(
NX) *
NY *
NZ, 10.0));
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],
371 return std::fabs(rx - 1.5) < 1e-12 && std::fabs(rz - 2.0) < 1e-12;
373 CHECK(set !=
nullptr);
376 for (
const auto& b : set->
blocks()) {
379 CHECK(ratios(b.curvature().metric,
"block cascade"));
380 CHECK(ratios(b.advector().metric,
"block advector"));
384int main(
int argc,
char** argv) {
385 Kokkos::initialize(argc, argv);
387 std::printf(
"VoF block container: overlap-robustness rules (vof_overlap_design), backend %s\n",
388 peclet::flow::SExec::name());
389 gateClipOnBlockPath();
390 gatePrototypePropagation();
394 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 ...
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,...
flow — host-facing Kokkos IBM Navier-Stokes solver (drop-in flow-style API).
long L3(int x, int y, int z, I3 e)
Kokkos::View< double *, SMem > SField
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)
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 double SIGMA
static constexpr double CY
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...