20#include <Kokkos_Core.hpp>
32 std::fprintf(stderr, "CHECK failed: %s\n at %s:%d\n", #cond, __FILE__, __LINE__); \
41 std::uint64_t next() {
42 std::uint64_t z = (s += 0x9E3779B97F4A7C15ULL);
43 z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
44 z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;
47 double uniform() {
return (
double)(next() >> 11) * (1.0 / 9007199254740992.0); }
54long double refVolume(
double mx,
double my,
double mz,
double alpha) {
55 long double a[3] = {std::fabs((
long double)mx), std::fabs((
long double)my),
56 std::fabs((
long double)mz)};
57 long double beta = (
long double)alpha;
66 for (
int i = 0;
i < 3; ++
i)
70 return beta > 0.0L ? 1.0L : 0.0L;
71 long double denom = 1.0L;
72 for (
int i = 0;
i <
d; ++
i)
74 for (
int i = 2;
i <=
d; ++
i)
76 long double sum = 0.0L;
77 for (
int mask = 0; mask < (1 <<
d); ++mask) {
80 for (
int i = 0;
i <
d; ++
i)
81 if (mask & (1 << i)) {
88 for (
int i = 0;
i <
d; ++
i)
90 sum += (
bits & 1) ? -p :
p;
92 long double v = sum /
denom;
93 return v < 0.0L ? 0.0L : (
v > 1.0L ? 1.0L :
v);
96double angleDeg(
const double a[3],
const double b[3]) {
97 const double na = std::sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
98 const double nb = std::sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2]);
99 if (!(na > 0.0) || !(nb > 0.0))
101 double d = (
a[0] *
b[0] +
a[1] *
b[1] +
a[2] *
b[2]) / (na * nb);
102 d =
d > 1.0 ? 1.0 : (
d < -1.0 ? -1.0 :
d);
103 return std::acos(d) * (180.0 /
M_PI);
110void makeSamples(
int n, std::vector<double>& mx, std::vector<double>& my, std::vector<double>& mz,
111 std::vector<double>&
vv, std::uint64_t
seed) {
117 for (
int i = 0;
i <
n; ++
i) {
119 const int fam =
i % 4;
125 }
while (m[0] * m[0] + m[1] * m[1] + m[2] * m[2] > 1.0 ||
126 m[0] * m[0] + m[1] * m[1] + m[2] * m[2] < 1e-12);
127 }
else if (
fam == 1) {
128 const double e1 = std::pow(10.0, -16.0 *
r.uniform());
129 const double e2 = std::pow(10.0, -16.0 *
r.uniform());
131 m[1] =
e1 * (
r.uniform() < 0.5 ? -1.0 : 1.0);
132 m[2] =
e2 * (
r.uniform() < 0.5 ? -1.0 : 1.0);
133 const int p = (
int)(
r.uniform() * 3.0) % 3;
135 const double t =
m[0];
139 const double t =
m[0];
143 }
else if (
fam == 2) {
144 m[0] =
m[1] =
m[2] = 0.0;
145 const int k = (
int)(
r.uniform() * 3.0) % 3;
146 m[
k] =
r.uniform() < 0.5 ? -1.0 : 1.0;
147 if (
r.uniform() < 0.5)
148 m[(k + 1) % 3] =
r.sym();
153 const int k = (
int)(
r.uniform() * 3.0) % 3;
156 const double s = std::fabs(m[0]) + std::fabs(m[1]) + std::fabs(m[2]);
160 const double u =
r.uniform();
164 vv[
i] = std::pow(10.0, -18.0 *
r.uniform());
166 vv[
i] = 1.0 - std::pow(10.0, -18.0 *
r.uniform());
172 std::printf(
"\n--- gate A: forward exactness ---\n");
174 auto chk = [&](
double mx,
double my,
double mz,
double a,
double expect,
const char*
what) {
175 const double v = plicVolume(mx, my, mz, a);
176 const double e = std::fabs(v -
expect);
180 std::fprintf(
stderr,
" %-28s V=%.17g expect=%.17g err=%.3e\n", what, v,
expect, e);
184 for (
int ax = 0;
ax < 3; ++
ax)
185 for (
int sg = -1;
sg <= 1;
sg += 2) {
186 double m[3] = {0, 0, 0};
189 chk(m[0], m[1], m[2], sg * 0.3, sg > 0 ? 0.3 : 0.7,
"axis aligned");
193 chk(0.5, 1.0 / 3.0, 1.0 / 6.0, 1.0 / 12.0, 1.0 / 288.0,
"case1 tetra");
195 chk(0.5, 1.0 / 3.0, 1.0 / 6.0, 0.25, 0.09375 - 0.09375 / 27.0,
"case2 one corner");
197 chk(0.5, 1.0 / 3.0, 1.0 / 6.0, 0.4, 0.306,
"case3 two corners");
199 chk(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 0.45,
200 (0.45 * 0.45 * 0.45 - 3.0 * std::pow(0.45 - 1.0 / 3.0, 3)) / (6.0 / 27.0),
"case4 three");
202 chk(0.6, 0.3, 0.1, 0.45, 0.25 / 0.6,
"case5 slab");
204 chk(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0,
"x+y+z<1");
205 chk(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 2.0 / 3.0, 5.0 / 6.0,
"x+y+z<2");
206 chk(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 0.5, 0.5,
"diagonal half");
207 chk(0.5, 0.5, 0.0, 0.25, 0.125,
"2D triangle");
208 chk(0.5, 0.25, 0.25, 0.5, 0.5,
"asym half");
209 chk(0.5, 0.5, 0.0, 0.5, 0.5,
"2D half");
211 chk(0.5, 0.3, 0.2, -0.1, 0.0,
"alpha<0");
212 chk(0.5, 0.3, 0.2, 1.5, 1.0,
"alpha>1");
213 std::printf(
"hand-computed planes (14 + 6 axis): max |dV| = %.3e (gate 1e-14)\n",
emax);
222 for (
int i = 0;
i < 200000; ++
i) {
228 }
while (m[0] * m[0] + m[1] * m[1] + m[2] * m[2] > 1.0);
229 const double s = std::fabs(m[0]) + std::fabs(m[1]) + std::fabs(m[2]);
235 const double amin = std::fmin(std::fmin(std::fabs(m[0]), std::fabs(m[1])), std::fabs(m[2]));
238 const double alpha = -0.1 + 1.2 *
r.uniform();
239 const double v = plicVolume(m[0], m[1], m[2], alpha);
240 const double e = std::fabs(v - (
double)
refVolume(m[0], m[1], m[2], alpha));
245 std::printf(
"vs inclusion-exclusion oracle (%d samples): max |dV| = %.3e (gate 1e-14)\n",
nsamp,
252 std::printf(
"\n--- gate B: analytic round trip ---\n");
253 const int N = 100000;
254 std::vector<double>
mx, my, mz,
vv;
258 for (
int i = 0;
i <
N; ++
i) {
259 const double a = plicAlpha(mx[i], my[i], mz[i],
vv[i]);
260 const double v2 = plicVolume(mx[i], my[i], mz[i], a);
261 const double e = std::fabs(
v2 -
vv[i]);
269 static const char*
fam[4] = {
"isotropic",
"near-axis ",
"degenerate",
"big-ratio "};
270 for (
int f = 0;
f < 4; ++
f)
271 std::printf(
" %s : max |dV| = %.3e\n",
fam[f],
emax[f]);
272 std::printf(
"round trip over %d samples: max |dV| = %.3e (gate 1e-13)\n",
N,
emaxAll);
273 std::printf(
" worst sample: m = (%.17g, %.17g, %.17g), V = %.17g\n", mx[
iworst], my[
iworst],
280 for (
int i = 0;
i < 20000; ++
i) {
286 }
while (m[0] * m[0] + m[1] * m[1] + m[2] * m[2] > 1.0);
287 const double s = std::fabs(m[0]) + std::fabs(m[1]) + std::fabs(m[2]);
294 std::fmin(0.0, std::fmin(m[0], 0.0) + std::fmin(m[1], 0.0) + std::fmin(m[2], 0.0));
295 const double alpha =
lo +
r.uniform() * (1.0 - 2.0 *
lo);
296 const double v = plicVolume(m[0], m[1], m[2], alpha);
302 const double a2 = plicAlpha(m[0], m[1], m[2], v);
303 amax = std::fmax(
amax, std::fabs(a2 - alpha));
305 std::printf(
"reverse round trip (alpha->V->alpha): max |d alpha| = %.3e (gate 1e-13)\n",
amax);
311 std::printf(
"\n--- gate C: slab / flux consistency ---\n");
312 std::vector<double>
mx, my, mz,
vv;
317 for (std::size_t i = 0;
i <
mx.size(); ++
i) {
318 const double a = plicAlpha(mx[i], my[i], mz[i],
vv[i]);
319 const double vRef = plicVolume(mx[i], my[i], mz[i], a);
320 for (
int d = 0;
d < 3; ++
d) {
324 if (std::memcmp(&
vf1, &
vRef,
sizeof(
double)) == 0)
326 const double f =
r.uniform();
328 const double hi =
plicSlabVolume(mx[i], my[i], mz[i], a, d, f, 1.0);
333 for (
int k = 0;
k <
K; ++
k)
334 acc +=
plicSlabVolume(mx[i], my[i], mz[i], a, d, (
double)k / K, (
double)(k + 1) / K);
338 std::printf(
"faceFluxVolume(f=1) vs plicVolume: max |dV| = %.3e, bitwise identical %d/%d\n",
340 std::printf(
"2-way additivity : max |dV| = %.3e (gate 1e-14)\n",
eAdd);
341 std::printf(
"7-way partition : max |dV| = %.3e (gate 1e-14)\n",
ePart);
350 for (
int i = 0;
i < 2000; ++
i) {
351 const double alpha =
r.uniform();
352 const double f =
r.uniform();
353 for (
int d = 0;
d < 3; ++
d) {
354 double m[3] = {0, 0, 0};
357 const double want = std::fmin(alpha, f);
358 eAx = std::fmax(
eAx, std::fabs(
got - want));
361 std::printf(
"axis-aligned slab closed form: max |dV| = %.3e (gate 1e-15)\n",
eAx);
368 for (
int k = 0;
k < 3; ++
k)
369 for (
int j = 0;
j < 3; ++
j)
370 for (
int i = 0;
i < 3; ++
i)
372 (j - 1) - 0.5, (k - 1) - 0.5, 1.0);
376 std::printf(
"\n--- gate D1: normals on exact planes ---\n");
381 double worst[3] = {0, 0, 0};
382 for (
int t = 0;
t <
N; ++
t) {
389 q =
m[0] *
m[0] +
m[1] *
m[1] +
m[2] *
m[2];
390 }
while (q > 1.0 || q < 1e-6);
391 const double s = std::fabs(m[0]) + std::fabs(m[1]) + std::fabs(m[2]);
396 const double px =
r.sym() * 0.5, py =
r.sym() * 0.5, pz =
r.sym() * 0.5;
397 const double alpha =
m[0] * px +
m[1] * py +
m[2] * pz;
398 double c[27],
mm[3],
yy[3];
415 std::printf(
"MYC : max %.4f deg, mean %.4f deg, %d/%d over 1 deg\n",
mycMax,
mycSum /
N,
417 std::printf(
" worst normal m = (%.6f, %.6f, %.6f)\n", worst[0], worst[1], worst[2]);
418 std::printf(
"Youngs : max %.4f deg, mean %.4f deg (reference, not gated)\n",
youngMax,
430 std::printf(
"\n--- gate D2: normal convergence on a refined sphere ---\n");
431 const double R = 0.35;
432 const double cx = 0.5 + 0.013, cy = 0.5 - 0.021, cz = 0.5 + 0.007;
433 const int NS[3] = {16, 32, 64};
436 for (
int g = 0;
g < 3; ++
g) {
438 const double h = 1.0 /
n;
439 std::vector<double>
C((std::size_t)n * n * n);
440 long double totVol = 0.0L;
441 for (
int k = 0;
k <
n; ++
k)
442 for (
int j = 0;
j <
n; ++
j)
443 for (
int i = 0;
i <
n; ++
i) {
445 C[(std::size_t)i + (std::size_t)
n * (
j + (std::size_t)n * k)] =
cf;
450 double sm = 0.0, sy = 0.0,
srec = 0.0,
vmax = 0.0;
452 for (
int k = 1;
k <
n - 1; ++
k)
453 for (
int j = 1;
j <
n - 1; ++
j)
454 for (
int i = 1;
i <
n - 1; ++
i) {
455 const double cc =
C[(std::size_t)i + (std::size_t)
n * (
j + (std::size_t)n * k)];
459 for (
int dk = -1;
dk <= 1; ++
dk)
460 for (
int dj = -1;
dj <= 1; ++
dj)
461 for (
int di = -1;
di <= 1; ++
di)
462 c[(
di + 1) + 3 * (
dj + 1) + 9 * (dk + 1)] =
463 C[(std::size_t)(i +
di) +
464 (std::size_t)n * ((j +
dj) + (std::size_t)n * (k + dk))];
466 const double ex = (
i + 0.5) * h - cx, ey = (j + 0.5) *
h - cy, ez = (
k + 0.5) * h - cz;
467 const double eN[3] = {ex, ey, ez};
475 const double alpha = plicAlpha(
mm[0],
mm[1],
mm[2], cc);
476 vmax = std::fmax(
vmax, std::fabs(plicVolume(
mm[0],
mm[1],
mm[2], alpha) - cc));
478 for (
int c3 = 0;
c3 <
K; ++
c3)
479 for (
int c2 = 0;
c2 <
K; ++
c2)
480 for (
int c1 = 0;
c1 <
K; ++
c1) {
481 const double xi = (
c1 + 0.5) / K,
et = (c2 + 0.5) /
K,
ze = (
c3 + 0.5) / K;
496 " N=%3d mixed %6ld reconstruction E1 %.4e | MYC normal L1 %.5f deg | Youngs "
497 "%.5f deg | init vol rel %.2e\n",
502 auto slope = [](
const int*
nn,
const double*
e) {
503 double sx = 0, sy = 0,
sxx = 0,
sxy = 0;
504 for (
int i = 0;
i < 3; ++
i) {
505 const double x = std::log((
double)
nn[i]),
y = std::log(e[i]);
516 "fitted order: reconstruction %.3f (GATE > 1.7) | MYC normal angle %.3f (recorded) |"
517 " Youngs %.3f | initSphere volume %.3f (gate > 1.7)\n",
519 std::printf(
" pairwise 16->32 / 32->64: reconstruction %.3f/%.3f, MYC normal %.3f/%.3f\n",
538 std::printf(
"\n--- gate E: device kernel vs serial host oracle ---\n");
539 const int N = 100000;
540 std::vector<double>
mx, my, mz,
vv;
543 using Dev = Kokkos::DefaultExecutionSpace;
544 Kokkos::View<double**, Kokkos::LayoutLeft, Kokkos::HostSpace>
hin(
"in",
N, 4);
545 for (
int i = 0;
i <
N; ++
i) {
551 auto din = Kokkos::create_mirror_view_and_copy(
Dev(),
hin);
552 Kokkos::View<double**, Kokkos::LayoutLeft, Dev>
dout(
"out",
N, 3);
553 Kokkos::parallel_for(
554 "peclet::flow::vof::plic_roundtrip", Kokkos::RangePolicy<Dev>(0,
N),
KOKKOS_LAMBDA(
int i) {
555 const double a = plicAlpha(
din(i, 0),
din(i, 1),
din(i, 2),
din(i, 3));
561 auto hout = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(),
dout);
565 for (
int i = 0;
i <
N; ++
i) {
566 const double a = plicAlpha(mx[i], my[i], mz[i],
vv[i]);
567 const double v = plicVolume(mx[i], my[i], mz[i], a);
569 const double ref[3] = {
a,
v,
f};
571 for (
int q = 0;
q < 3; ++
q) {
572 if (std::memcmp(&ref[q], &
hout(i, q),
sizeof(
double)) != 0)
580 const bool sameBackend = std::is_same<Dev, Kokkos::DefaultHostExecutionSpace>::value;
581 std::printf(
"exec space %s (host default: %s)\n", Dev::name(),
582 Kokkos::DefaultHostExecutionSpace::name());
583 std::printf(
"bitwise identical %d/%d elements, max |diff| = %.3e\n",
nbit,
N,
dmax);
584 std::printf(
"device-side round-trip max |dV| = %.3e (gate 1e-13)\n",
devRt);
591 std::printf(
" (cross-backend run: tolerance policy, not bitwise)\n");
610 std::printf(
"\n=== K1: PLIC on anisotropic cells (metric-free volumes) ===\n");
612 const int N = 200000;
616 for (
int i = 0;
i <
N; ++
i) {
618 const double h[3] = {std::pow(2.0, -2.0 + 4.0 *
r.uniform()),
619 std::pow(2.0, -2.0 + 4.0 *
r.uniform()),
620 std::pow(2.0, -2.0 + 4.0 *
r.uniform())};
621 const VofMetric
g{{
h[0],
h[1],
h[2]}};
627 }
while (n[0] * n[0] + n[1] * n[1] + n[2] * n[2] > 1.0);
631 double mi[3] = {
n[0] *
h[0],
n[1] *
h[1],
n[2] *
h[2]};
632 const double s1 = std::fabs(
mi[0]) + std::fabs(
mi[1]) + std::fabs(
mi[2]);
635 const double amin = std::fmin(std::fmin(std::fabs(
mi[0]), std::fabs(
mi[1])), std::fabs(
mi[2]));
636 if (
amin / s1 < 1e-2)
641 const double x0 =
r.sym(), y0 =
r.sym(),
z0 =
r.sym();
642 const double px =
x0 +
h[0] *
r.uniform(), py = y0 +
h[1] *
r.uniform(),
643 pz =
z0 +
h[2] *
r.uniform();
644 const double aG =
n[0] * px +
n[1] * py +
n[2] * pz;
648 const double aLoc =
aG - (
n[0] *
x0 +
n[1] * y0 +
n[2] *
z0);
650 worst = std::fmax(worst, std::fabs(
got - ref));
653 const double f = 0.25 + 0.5 *
r.uniform();
658 std::printf(
" max |frac - oracle| over %ld well-conditioned (n, alpha, h in [0.25,4]^3): %.3e\n",
660 std::printf(
" max |slab - oracle| (stretched donor flux) : %.3e\n",
662 CHECK(worst < 1e-14);
668 std::vector<double>
mx, my, mz,
vv;
672 for (
int i = 0;
i <
N; ++
i) {
673 const double hh = 0.5 + r2.uniform();
675 const double x0 = r2.sym(), y0 = r2.sym(),
z0 = r2.sym();
676 const double aG =
mx[
i] * (
x0 +
hh * r2.uniform()) + my[i] * (y0 +
hh * r2.uniform()) +
677 mz[i] * (
z0 +
hh * r2.uniform());
684 std::printf(
" isotropic metric == the scalar-h helper, bitwise: %ld/%ld\n",
bitwise, tot);
691 const VofMetric
one{};
692 const VofMetric
g{{0.5, 2.0, 1.25}};
696 for (
int i = 0;
i < 2000; ++
i) {
697 double m[3] = {
r3.sym(),
r3.sym(),
r3.sym()};
698 const double q = std::sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]);
703 const double s1 = vofPhysNormal(m, one,
n1);
704 if (s1 == 1.0 &&
n1[0] == m[0] / q &&
n1[1] == m[1] / q &&
n1[2] == m[2] / q)
706 double n2[3],
mi[3],
n3[3];
707 if (vofPhysNormal(m, g,
n2) > 0.0) {
708 vofIndexNormal(
n2, g,
mi);
709 vofPhysNormal(
mi, g,
n3);
710 for (
int d = 0;
d < 3; ++
d)
711 rt = std::fmax(
rt, std::fabs(
n3[d] -
n2[d]));
715 " vofPhysNormal at the unit metric == m/|m| exactly: %ld/%ld;"
716 " H^-1(H n) round trip %.3e\n",
726 Kokkos::initialize(
argc, argv);
738 std::printf(
"\nOK\n");
exact(Ds, K, hc, gs, nmax=1)
flow — thin shim: the container-free VoF PLIC geometric toolbox (rung V0) now live in core.