core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
curvature.hpp
Go to the documentation of this file.
1
117#ifndef PECLET_CORE_VOF_CURVATURE_HPP
118#define PECLET_CORE_VOF_CURVATURE_HPP
119
120#include <Kokkos_Core.hpp>
121#include <Kokkos_MathematicalFunctions.hpp>
122
124
126
127// ---------------------------------------------------------------------------------------------
128// cascade bookkeeping
129// ---------------------------------------------------------------------------------------------
130
144
148inline constexpr int kHfColumn = 7;
150inline constexpr int kPvHalf = 2;
155inline constexpr double kPvWeightWidth = 2.5;
159inline constexpr double kPtWeightWidth = 3.5;
160
164inline constexpr double kHfPureEps = 1e-10;
165
185KOKKOS_INLINE_FUNCTION bool hfIsFull(double c, double eps) {
186 return c >= 1.0 - (eps > kHfPureEps ? eps : kHfPureEps);
187}
188KOKKOS_INLINE_FUNCTION bool hfIsEmpty(double c, double eps) {
189 return c <= (eps > kHfPureEps ? eps : kHfPureEps);
190}
191
192// ---------------------------------------------------------------------------------------------
193// tier 1 / 2 — height functions
194// ---------------------------------------------------------------------------------------------
195
233KOKKOS_INLINE_FUNCTION bool hfColumnHeight(const double* col, int nh, double& h, int& orient,
234 double monoTol, double pureEps = kHfPureEps) {
235 h = 0.0;
236 orient = 0;
237 const int R = nh / 2;
238 double lo = 0.0, hi = 0.0;
239 for (int k = 0; k < R; ++k) {
240 lo += col[k];
241 hi += col[nh - 1 - k];
242 }
243 if (lo > hi)
244 orient = 1;
245 else if (hi > lo)
246 orient = -1;
247 else
248 return false;
249
250 int a = -1, b = -1;
251 if (orient == 1) {
252 for (int k = R; k >= 0; --k)
253 if (hfIsFull(col[k], pureEps)) {
254 a = k;
255 break;
256 }
257 for (int k = R; k < nh; ++k)
258 if (hfIsEmpty(col[k], pureEps)) {
259 b = k;
260 break;
261 }
262 } else {
263 for (int k = R; k >= 0; --k)
264 if (hfIsEmpty(col[k], pureEps)) {
265 a = k;
266 break;
267 }
268 for (int k = R; k < nh; ++k)
269 if (hfIsFull(col[k], pureEps)) {
270 b = k;
271 break;
272 }
273 }
274 if (a < 0 || b < 0)
275 return false;
276
277 double s = 0.0;
278 for (int k = a; k <= b; ++k) {
279 s += col[k];
280 // orient = +1 => non-increasing; orient = -1 => non-decreasing.
281 if (k < b && (col[k + 1] - col[k]) * orient > monoTol)
282 return false;
283 }
284 h = (orient == 1) ? (s + static_cast<double>(a - R) - 0.5)
285 : (s - static_cast<double>(b - R) - 0.5);
286 return true;
287}
288
296KOKKOS_INLINE_FUNCTION double hfPatchKappa(const double h[9]) {
297 const double hx = 0.5 * (h[2 + 3 * 1] - h[0 + 3 * 1]);
298 const double hy = 0.5 * (h[1 + 3 * 2] - h[1 + 3 * 0]);
299 const double hxx = h[2 + 3 * 1] - 2.0 * h[1 + 3 * 1] + h[0 + 3 * 1];
300 const double hyy = h[1 + 3 * 2] - 2.0 * h[1 + 3 * 1] + h[1 + 3 * 0];
301 const double hxy = 0.25 * (h[2 + 3 * 2] - h[0 + 3 * 2] - h[2 + 3 * 0] + h[0 + 3 * 0]);
302 const double q = 1.0 + hx * hx + hy * hy;
303 const double num = hxx + hyy + hxx * hy * hy + hyy * hx * hx - 2.0 * hxy * hx * hy;
304 // kappa = 2H and H = -num / (2 q^{3/2}).
305 return -num / (q * Kokkos::sqrt(q));
306}
307
327KOKKOS_INLINE_FUNCTION double hfPatchKappa(const double h[9], const VofMetric& g, int d) {
328 const int d1 = (d + 1) % 3, d2 = (d + 2) % 3;
329 const double r1 = g.h[d] / g.h[d1], r2 = g.h[d] / g.h[d2];
330 const double hx = 0.5 * (h[2 + 3 * 1] - h[0 + 3 * 1]) * r1;
331 const double hy = 0.5 * (h[1 + 3 * 2] - h[1 + 3 * 0]) * r2;
332 const double hxx = (h[2 + 3 * 1] - 2.0 * h[1 + 3 * 1] + h[0 + 3 * 1]) * r1 / g.h[d1];
333 const double hyy = (h[1 + 3 * 2] - 2.0 * h[1 + 3 * 1] + h[1 + 3 * 0]) * r2 / g.h[d2];
334 const double hxy =
335 0.25 * (h[2 + 3 * 2] - h[0 + 3 * 2] - h[2 + 3 * 0] + h[0 + 3 * 0]) * r1 / g.h[d2];
336 const double q = 1.0 + hx * hx + hy * hy;
337 const double num = hxx + hyy + hxx * hy * hy + hyy * hx * hx - 2.0 * hxy * hx * hy;
338 return -num / (q * Kokkos::sqrt(q));
339}
340
341// ---------------------------------------------------------------------------------------------
342// small linear algebra + geometry shared by the fit
343// ---------------------------------------------------------------------------------------------
344
348KOKKOS_INLINE_FUNCTION void curvFrame(const double n[3], double t1[3], double t2[3]) {
349 int a = 0;
350 double m = Kokkos::fabs(n[0]);
351 if (Kokkos::fabs(n[1]) < m) {
352 m = Kokkos::fabs(n[1]);
353 a = 1;
354 }
355 if (Kokkos::fabs(n[2]) < m)
356 a = 2;
357 double e[3] = {0.0, 0.0, 0.0};
358 e[a] = 1.0;
359 // t1 = normalize(e x n)
360 t1[0] = e[1] * n[2] - e[2] * n[1];
361 t1[1] = e[2] * n[0] - e[0] * n[2];
362 t1[2] = e[0] * n[1] - e[1] * n[0];
363 const double s = Kokkos::sqrt(t1[0] * t1[0] + t1[1] * t1[1] + t1[2] * t1[2]);
364 const double inv = (s > 0.0) ? 1.0 / s : 0.0;
365 t1[0] *= inv;
366 t1[1] *= inv;
367 t1[2] *= inv;
368 // t2 = n x t1
369 t2[0] = n[1] * t1[2] - n[2] * t1[1];
370 t2[1] = n[2] * t1[0] - n[0] * t1[2];
371 t2[2] = n[0] * t1[1] - n[1] * t1[0];
372}
373
376KOKKOS_INLINE_FUNCTION double wendlandWeight(double r, double d) {
377 if (!(d > 0.0))
378 return 1.0;
379 const double q = r / d;
380 if (q >= 1.0)
381 return 0.0;
382 const double t = 1.0 - q;
383 const double t2 = t * t;
384 return (1.0 + 4.0 * q) * t2 * t2;
385}
386
390KOKKOS_INLINE_FUNCTION double paraboloidKappa(const double a[6]) {
391 const double q = 1.0 + a[1] * a[1] + a[2] * a[2];
392 const double num = a[5] * a[1] * a[1] - a[4] * a[1] * a[2] + a[3] * a[2] * a[2] + a[3] + a[5];
393 return -2.0 * num / (q * Kokkos::sqrt(q));
394}
395
402KOKKOS_INLINE_FUNCTION bool curvSolveSym(const double Ain[6][6], const double bin[6], int n,
403 double x[6], double pivTol) {
404 double d[6];
405 for (int i = 0; i < n; ++i) {
406 if (!(Ain[i][i] > 0.0))
407 return false;
408 d[i] = 1.0 / Kokkos::sqrt(Ain[i][i]);
409 }
410 double A[6][6], b[6];
411 for (int i = 0; i < n; ++i) {
412 b[i] = bin[i] * d[i];
413 for (int j = 0; j < n; ++j)
414 A[i][j] = Ain[i][j] * d[i] * d[j];
415 }
416 // LDL^T in place (A[i][i] holds D, A[i][j] j<i holds L).
417 for (int i = 0; i < n; ++i) {
418 double s = A[i][i];
419 for (int k = 0; k < i; ++k)
420 s -= A[i][k] * A[i][k] * A[k][k];
421 if (!(s > pivTol)) // NaN-safe: a NaN pivot fails the test
422 return false;
423 A[i][i] = s;
424 for (int j = i + 1; j < n; ++j) {
425 double t = A[j][i];
426 for (int k = 0; k < i; ++k)
427 t -= A[j][k] * A[i][k] * A[k][k];
428 A[j][i] = t / s;
429 }
430 }
431 // forward / diagonal / backward
432 double y[6];
433 for (int i = 0; i < n; ++i) {
434 double s = b[i];
435 for (int k = 0; k < i; ++k)
436 s -= A[i][k] * y[k];
437 y[i] = s;
438 }
439 for (int i = 0; i < n; ++i)
440 y[i] /= A[i][i];
441 for (int i = n - 1; i >= 0; --i) {
442 double s = y[i];
443 for (int k = i + 1; k < n; ++k)
444 s -= A[k][i] * x[k];
445 x[i] = s;
446 }
447 for (int i = 0; i < n; ++i)
448 x[i] *= d[i];
449 return true;
450}
451
452// ---------------------------------------------------------------------------------------------
453// the PLIC polygon: plane ^ unit cube
454// ---------------------------------------------------------------------------------------------
455
465KOKKOS_INLINE_FUNCTION int plicPolygon(double mx, double my, double mz, double alpha,
466 double v[8][3]) {
467 const double m[3] = {mx, my, mz};
468 const double nrm2 = m[0] * m[0] + m[1] * m[1] + m[2] * m[2];
469 if (!(nrm2 > 0.0))
470 return 0;
471
472 int nv = 0;
473 for (int q = 0; q < 8 && nv < 8; ++q) {
474 const double A[3] = {static_cast<double>(q & 1), static_cast<double>((q >> 1) & 1),
475 static_cast<double>((q >> 2) & 1)};
476 const double fA = m[0] * A[0] + m[1] * A[1] + m[2] * A[2] - alpha;
477 for (int a = 0; a < 3 && nv < 8; ++a) {
478 if ((q >> a) & 1)
479 continue; // walk each edge once, from its low corner
480 double B[3] = {A[0], A[1], A[2]};
481 B[a] = 1.0;
482 const double fB = fA + m[a];
483 // Straddle test with a half-open convention so a corner exactly on the plane is counted by
484 // exactly one of its edges.
485 const bool cross = (fA <= 0.0 && fB > 0.0) || (fB <= 0.0 && fA > 0.0);
486 if (!cross)
487 continue;
488 const double den = fA - fB;
489 const double t = (den != 0.0) ? fA / den : 0.0;
490 double p[3] = {A[0], A[1], A[2]};
491 p[a] = A[a] + t * (B[a] - A[a]);
492 // de-duplicate (a corner hit is found by up to three edges)
493 bool dup = false;
494 for (int k = 0; k < nv; ++k) {
495 const double dx = p[0] - v[k][0], dy = p[1] - v[k][1], dz = p[2] - v[k][2];
496 if (dx * dx + dy * dy + dz * dz < 1e-24) {
497 dup = true;
498 break;
499 }
500 }
501 if (dup)
502 continue;
503 v[nv][0] = p[0];
504 v[nv][1] = p[1];
505 v[nv][2] = p[2];
506 ++nv;
507 }
508 }
509 if (nv < 3)
510 return nv;
511
512 // Angular sort about the centroid in the plane's own frame.
513 const double invn = 1.0 / Kokkos::sqrt(nrm2);
514 const double nh[3] = {m[0] * invn, m[1] * invn, m[2] * invn};
515 double e1[3], e2[3];
516 curvFrame(nh, e1, e2);
517 double cx = 0.0, cy = 0.0, cz = 0.0;
518 for (int k = 0; k < nv; ++k) {
519 cx += v[k][0];
520 cy += v[k][1];
521 cz += v[k][2];
522 }
523 const double inv = 1.0 / static_cast<double>(nv);
524 cx *= inv;
525 cy *= inv;
526 cz *= inv;
527 double ang[8];
528 for (int k = 0; k < nv; ++k) {
529 const double dx = v[k][0] - cx, dy = v[k][1] - cy, dz = v[k][2] - cz;
530 ang[k] =
531 Kokkos::atan2(dx * e2[0] + dy * e2[1] + dz * e2[2], dx * e1[0] + dy * e1[1] + dz * e1[2]);
532 }
533 for (int i = 1; i < nv; ++i) { // insertion sort, nv <= 6
534 const double a0 = ang[i];
535 const double p0 = v[i][0], p1 = v[i][1], p2 = v[i][2];
536 int j = i - 1;
537 while (j >= 0 && ang[j] > a0) {
538 ang[j + 1] = ang[j];
539 v[j + 1][0] = v[j][0];
540 v[j + 1][1] = v[j][1];
541 v[j + 1][2] = v[j][2];
542 --j;
543 }
544 ang[j + 1] = a0;
545 v[j + 1][0] = p0;
546 v[j + 1][1] = p1;
547 v[j + 1][2] = p2;
548 }
549 return nv;
550}
551
553KOKKOS_INLINE_FUNCTION void polygonAreaCentroid(const double v[8][3], int nv, double ctr[3],
554 double& area) {
555 ctr[0] = ctr[1] = ctr[2] = 0.0;
556 area = 0.0;
557 if (nv < 3)
558 return;
559 for (int k = 1; k + 1 < nv; ++k) {
560 const double a1[3] = {v[k][0] - v[0][0], v[k][1] - v[0][1], v[k][2] - v[0][2]};
561 const double a2[3] = {v[k + 1][0] - v[0][0], v[k + 1][1] - v[0][1], v[k + 1][2] - v[0][2]};
562 const double cx = a1[1] * a2[2] - a1[2] * a2[1];
563 const double cy = a1[2] * a2[0] - a1[0] * a2[2];
564 const double cz = a1[0] * a2[1] - a1[1] * a2[0];
565 const double t = 0.5 * Kokkos::sqrt(cx * cx + cy * cy + cz * cz);
566 area += t;
567 ctr[0] += t * (v[0][0] + v[k][0] + v[k + 1][0]) / 3.0;
568 ctr[1] += t * (v[0][1] + v[k][1] + v[k + 1][1]) / 3.0;
569 ctr[2] += t * (v[0][2] + v[k][2] + v[k + 1][2]) / 3.0;
570 }
571 if (area > 0.0) {
572 const double inv = 1.0 / area;
573 ctr[0] *= inv;
574 ctr[1] *= inv;
575 ctr[2] *= inv;
576 }
577}
578
593KOKKOS_INLINE_FUNCTION void polygonMoments2d(const double xy[8][2], int nv, double s[6]) {
594 for (int i = 0; i < 6; ++i)
595 s[i] = 0.0;
596 if (nv < 3)
597 return;
598 for (int k = 0; k < nv; ++k) {
599 const int k1 = (k + 1 == nv) ? 0 : k + 1;
600 const double x0 = xy[k][0], y0 = xy[k][1];
601 const double x1 = xy[k1][0], y1 = xy[k1][1];
602 const double dx = x1 - x0, dy = y1 - y0;
603 if (dy == 0.0)
604 continue;
605 s[0] += dy * 0.5 * (x0 + x1);
606 s[1] += dy * (x0 * x0 + x0 * x1 + x1 * x1) / 6.0;
607 s[2] += dy * (2.0 * x0 * y0 + x0 * y1 + x1 * y0 + 2.0 * x1 * y1) / 6.0;
608 s[3] += dy * (x0 + x1) * (x0 * x0 + x1 * x1) / 12.0;
609 // int_0^1 x^2 y dt, x = x0 + t dx, y = y0 + t dy
610 s[4] += dy * 0.5 *
611 (x0 * x0 * y0 + 0.5 * x0 * x0 * dy + x0 * dx * y0 + (2.0 / 3.0) * x0 * dx * dy +
612 dx * dx * y0 / 3.0 + 0.25 * dx * dx * dy);
613 // int_0^1 x y^2 dt
614 s[5] += dy * (x0 * y0 * y0 + x0 * y0 * dy + x0 * dy * dy / 3.0 + 0.5 * dx * y0 * y0 +
615 (2.0 / 3.0) * dx * y0 * dy + 0.25 * dx * dy * dy);
616 }
617}
618
619// ---------------------------------------------------------------------------------------------
620// tier 2b — the mixed height function: a paraboloid through the interface positions that the
621// consistent columns of ALL THREE directions do provide
622// ---------------------------------------------------------------------------------------------
623
637struct PtFit {
638 double A[6][6];
639 double b[6];
640 int npt;
641};
642
644 for (int i = 0; i < 6; ++i) {
645 f.b[i] = 0.0;
646 for (int j = 0; j < 6; ++j)
647 f.A[i][j] = 0.0;
648 }
649 f.npt = 0;
650}
651
656KOKKOS_INLINE_FUNCTION void ptFitAdd(PtFit& f, const double X[3], const double org[3],
657 const double t1[3], const double t2[3], const double nn[3],
658 double dW, const VofMetric& g) {
659 const double di[3] = {X[0] - org[0], X[1] - org[1], X[2] - org[2]};
660 // Phase 3 (V2.4): the frame is orthonormal in PHYSICAL space, so the offsets are physical.
661 // `g.h = {1,1,1}` makes this `di * 1.0`, i.e. today's numbers bit for bit.
662 double d[3];
663 g.toPhys(di, d);
664 const double x = d[0] * t1[0] + d[1] * t1[1] + d[2] * t1[2];
665 const double y = d[0] * t2[0] + d[1] * t2[1] + d[2] * t2[2];
666 const double z = d[0] * nn[0] + d[1] * nn[1] + d[2] * nn[2];
667 const double w = wendlandWeight(Kokkos::sqrt(x * x + y * y + z * z), dW);
668 if (!(w > 0.0))
669 return;
670 const double phi[6] = {1.0, x, y, x * x, x * y, y * y};
671 for (int i = 0; i < 6; ++i) {
672 f.b[i] += w * phi[i] * z;
673 for (int j = 0; j < 6; ++j)
674 f.A[i][j] += w * phi[i] * phi[j];
675 }
676 ++f.npt;
677}
678
680KOKKOS_INLINE_FUNCTION void ptFitAdd(PtFit& f, const double X[3], const double org[3],
681 const double t1[3], const double t2[3], const double nn[3],
682 double dW) {
683 ptFitAdd(f, X, org, t1, t2, nn, dW, VofMetric{});
684}
685
687KOKKOS_INLINE_FUNCTION bool ptFitSolve(const PtFit& f, double a[6], bool& red) {
688 red = false;
689 for (int i = 0; i < 6; ++i)
690 a[i] = 0.0;
691 if (f.npt >= 6 && curvSolveSym(f.A, f.b, 6, a, 1e-12))
692 return true;
693 if (f.npt < 3)
694 return false;
695 const int id[3] = {0, 3, 5};
696 double Ar[6][6], br[6], ar[6];
697 for (int i = 0; i < 3; ++i) {
698 br[i] = f.b[id[i]];
699 for (int j = 0; j < 3; ++j)
700 Ar[i][j] = f.A[id[i]][id[j]];
701 }
702 if (!curvSolveSym(Ar, br, 3, ar, 1e-12))
703 return false;
704 a[0] = ar[0];
705 a[3] = ar[1];
706 a[5] = ar[2];
707 red = true;
708 return true;
709}
710
711// ---------------------------------------------------------------------------------------------
712// tier 3 — the PLIC-volumetric paraboloid fit (Jibben 2019 / Han 2024 "PV")
713// ---------------------------------------------------------------------------------------------
714
717struct PvFit {
718 double A[6][6];
719 double b[6];
720 int npoly;
721};
722
724 for (int i = 0; i < 6; ++i) {
725 f.b[i] = 0.0;
726 for (int j = 0; j < 6; ++j)
727 f.A[i][j] = 0.0;
728 }
729 f.npoly = 0;
730}
731
752struct PvTerm {
753 double w, B, s[6];
754 bool ok;
755};
756
762KOKKOS_INLINE_FUNCTION bool pvTermNormal(double mx, double my, double mz, const double t1[3],
763 const double t2[3], const double nn[3], double cosMin,
764 const VofMetric& g, double np[3]) {
765 const double n2 = mx * mx + my * my + mz * mz;
766 if (!(n2 > 0.0))
767 return false;
768 // Phase 3 (V2.4): the polygon's own normal enters the fit as a PHYSICAL direction (the frame is
769 // physical), so it is pulled back through H^-1 and renormalized. At g.h = {1,1,1} the pullback
770 // is `m/1.0` and `invn` is the same reciprocal square root as before — bitwise.
771 const double mi[3] = {mx / g.h[0], my / g.h[1], mz / g.h[2]};
772 const double invn = 1.0 / Kokkos::sqrt(mi[0] * mi[0] + mi[1] * mi[1] + mi[2] * mi[2]);
773 np[0] = (mi[0] * t1[0] + mi[1] * t1[1] + mi[2] * t1[2]) * invn;
774 np[1] = (mi[0] * t2[0] + mi[1] * t2[1] + mi[2] * t2[2]) * invn;
775 np[2] = (mi[0] * nn[0] + mi[1] * nn[1] + mi[2] * nn[2]) * invn;
776 if (!(np[2] > cosMin))
777 return false;
778 return true;
779}
780
791KOKKOS_INLINE_FUNCTION bool pvTermPolygon(PvTerm& t, const double np[3], const double v[8][3],
792 int nv, const double off[3], const double org[3],
793 const double t1[3], const double t2[3],
794 const double nn[3], double dW, const VofMetric& g) {
795 t.ok = false;
796 // cell-local [0,1]^3 -> target-centred cell units -> the fit frame
797 double xy[8][2];
798 double zc = 0.0;
799 double px = 0.0, py = 0.0;
800 for (int k = 0; k < nv; ++k) {
801 const double Xi[3] = {off[0] + v[k][0] - 0.5 - org[0], off[1] + v[k][1] - 0.5 - org[1],
802 off[2] + v[k][2] - 0.5 - org[2]};
803 double X[3];
804 g.toPhys(Xi, X); // index displacement -> physical (identity at g.h = {1,1,1})
805 xy[k][0] = X[0] * t1[0] + X[1] * t1[1] + X[2] * t1[2];
806 xy[k][1] = X[0] * t2[0] + X[1] * t2[1] + X[2] * t2[2];
807 const double z = X[0] * nn[0] + X[1] * nn[1] + X[2] * nn[2];
808 px += xy[k][0];
809 py += xy[k][1];
810 zc += z;
811 }
812 const double invv = 1.0 / static_cast<double>(nv);
813 px *= invv;
814 py *= invv;
815 zc *= invv;
816
817 double s[6];
818 polygonMoments2d(xy, nv, s);
819 if (!(Kokkos::fabs(s[0]) > 1e-14))
820 return false;
821
822 // the polygon's own plane in the fit frame: z' = b0 + b1 x' + b2 y'
823 const double b1 = -np[0] / np[2], b2 = -np[1] / np[2];
824 const double b0 = zc - b1 * px - b2 * py;
825
826 const double r = Kokkos::sqrt(px * px + py * py + zc * zc);
827 const double w = wendlandWeight(r, dW);
828 if (!(w > 0.0))
829 return false;
830
831 const double B = b0 * s[0] + b1 * s[1] + b2 * s[2];
832 t.w = w;
833 t.B = B;
834 for (int i = 0; i < 6; ++i)
835 t.s[i] = s[i];
836 t.ok = true;
837 return true;
838}
839
847KOKKOS_INLINE_FUNCTION bool pvFitTerm(PvTerm& t, double mx, double my, double mz, double alpha,
848 const double off[3], const double org[3], const double t1[3],
849 const double t2[3], const double nn[3], double dW,
850 double cosMin, const VofMetric& g) {
851 t.ok = false;
852 double np[3];
853 if (!pvTermNormal(mx, my, mz, t1, t2, nn, cosMin, g, np))
854 return false;
855 double v[8][3];
856 const int nv = plicPolygon(mx, my, mz, alpha, v);
857 if (nv < 3)
858 return false;
859 return pvTermPolygon(t, np, v, nv, off, org, t1, t2, nn, dW, g);
860}
861
865 const double w = t.w, B = t.B;
866 const double* s = t.s;
867 for (int i = 0; i < 6; ++i) {
868 f.b[i] += w * s[i] * B;
869 for (int j = 0; j < 6; ++j)
870 f.A[i][j] += w * s[i] * s[j];
871 }
872 ++f.npoly;
873}
874
878KOKKOS_INLINE_FUNCTION bool pvFitAdd(PvFit& f, double mx, double my, double mz, double alpha,
879 const double off[3], const double org[3], const double t1[3],
880 const double t2[3], const double nn[3], double dW,
881 double cosMin, const VofMetric& g) {
882 PvTerm t;
883 if (!pvFitTerm(t, mx, my, mz, alpha, off, org, t1, t2, nn, dW, cosMin, g))
884 return false;
885 pvFitAccum(f, t);
886 return true;
887}
888
890KOKKOS_INLINE_FUNCTION bool pvFitAdd(PvFit& f, double mx, double my, double mz, double alpha,
891 const double off[3], const double org[3], const double t1[3],
892 const double t2[3], const double nn[3], double dW,
893 double cosMin) {
894 return pvFitAdd(f, mx, my, mz, alpha, off, org, t1, t2, nn, dW, cosMin, VofMetric{});
895}
896
908KOKKOS_INLINE_FUNCTION bool pvFitSolve(const PvFit& f, double a[6], bool& red) {
909 red = false;
910 for (int i = 0; i < 6; ++i)
911 a[i] = 0.0;
912 if (f.npoly >= 6 && curvSolveSym(f.A, f.b, 6, a, 1e-12))
913 return true;
914 if (f.npoly < 3)
915 return false;
916 // reduced {1, x^2, y^2} == indices 0, 3, 5 of the full system
917 const int id[3] = {0, 3, 5};
918 double Ar[6][6], br[6], ar[6];
919 for (int i = 0; i < 3; ++i) {
920 br[i] = f.b[id[i]];
921 for (int j = 0; j < 3; ++j)
922 Ar[i][j] = f.A[id[i]][id[j]];
923 }
924 if (!curvSolveSym(Ar, br, 3, ar, 1e-12))
925 return false;
926 a[0] = ar[0];
927 a[1] = 0.0;
928 a[2] = 0.0;
929 a[3] = ar[1];
930 a[4] = 0.0;
931 a[5] = ar[2];
932 red = true;
933 return true;
934}
935
936// ---------------------------------------------------------------------------------------------
937// tier 3 at cost (design G, flow `doc/vof_curvature_cost_design.md` §5.1): per-cell caches of the
938// PLIC polygon data, built ONCE per curvature pass, and the per-entry form of the accumulation
939// ---------------------------------------------------------------------------------------------
940
951 const double w = t.w, B = t.B;
952 const double* s = t.s;
953 for (int i = 0; i < 6; ++i) {
954 f.b[i] += w * s[i] * B;
955 for (int j = 0; j <= i; ++j)
956 f.A[i][j] += w * s[i] * s[j];
957 }
958 ++f.npoly;
959}
960
963inline constexpr int kPvEntries = 27;
964
970KOKKOS_INLINE_FUNCTION void pvFitAccumEntry(double& a, const PvTerm& t, int e) {
971 const double w = t.w;
972 if (e >= 21) {
973 const int i = e - 21;
974 a += w * t.s[i] * t.B;
975 return;
976 }
977 int i = 0;
978 while ((i + 1) * (i + 2) / 2 <= e)
979 ++i;
980 const int j = e - i * (i + 1) / 2;
981 a += w * t.s[i] * t.s[j];
982}
983
986 for (int i = 0; i < 6; ++i) {
987 f.b[i] = ent[21 + i];
988 for (int j = 0; j < 6; ++j)
989 f.A[i][j] = (j <= i) ? ent[i * (i + 1) / 2 + j] : 0.0;
990 }
991 f.npoly = npoly;
992}
993
997struct alignas(32) PvPolygon {
998 double v[8][3];
999 double vbar[3];
1000 int nv;
1001};
1002
1004KOKKOS_INLINE_FUNCTION void pvPolygonBuild(double mx, double my, double mz, double alpha,
1005 PvPolygon& P) {
1006 P.nv = plicPolygon(mx, my, mz, alpha, P.v);
1007 P.vbar[0] = P.vbar[1] = P.vbar[2] = 0.0;
1008 if (P.nv == 0)
1009 return;
1010 double sx = 0.0, sy = 0.0, sz = 0.0;
1011 for (int k = 0; k < P.nv; ++k) {
1012 sx += P.v[k][0];
1013 sy += P.v[k][1];
1014 sz += P.v[k][2];
1015 }
1016 const double inv = 1.0 / static_cast<double>(P.nv);
1017 P.vbar[0] = sx * inv - 0.5;
1018 P.vbar[1] = sy * inv - 0.5;
1019 P.vbar[2] = sz * inv - 0.5;
1020}
1021
1033KOKKOS_INLINE_FUNCTION bool pvOutsideSupport(const PvPolygon& P, const double off[3],
1034 const double org[3], double dW, const VofMetric& g) {
1035 if (P.nv < 3)
1036 return false;
1037 if (!(dW > 0.0))
1038 return false;
1039 const double Xi[3] = {P.vbar[0] + off[0] - org[0], P.vbar[1] + off[1] - org[1],
1040 P.vbar[2] + off[2] - org[2]};
1041 double X[3];
1042 g.toPhys(Xi, X);
1043 return X[0] * X[0] + X[1] * X[1] + X[2] * X[2] > dW * dW * (1.0 + 1e-9);
1044}
1045
1048struct alignas(64) PvMoments {
1049 double n[3];
1050 double c[3];
1051 double a;
1052 double m1[3];
1053 double m2[6];
1054};
1055
1063KOKKOS_INLINE_FUNCTION void pvMomentsBuild(double mx, double my, double mz, double alpha,
1064 const VofMetric& g, PvMoments& P) {
1065 for (int a = 0; a < 3; ++a) {
1066 P.n[a] = 0.0;
1067 P.c[a] = 0.0;
1068 P.m1[a] = 0.0;
1069 }
1070 P.a = 0.0;
1071 for (int q = 0; q < 6; ++q)
1072 P.m2[q] = 0.0;
1073 const double m[3] = {mx, my, mz};
1074 if (vofPhysNormalInv(m, g, P.n) <= 0.0)
1075 return;
1076 double v[8][3];
1077 const int nv = plicPolygon(mx, my, mz, alpha, v);
1078 if (nv < 3)
1079 return;
1080 double Y[8][3];
1081 double sc[3] = {0.0, 0.0, 0.0};
1082 for (int k = 0; k < nv; ++k) {
1083 const double Xi[3] = {v[k][0] - 0.5, v[k][1] - 0.5, v[k][2] - 0.5};
1084 g.toPhys(Xi, Y[k]);
1085 sc[0] += Y[k][0];
1086 sc[1] += Y[k][1];
1087 sc[2] += Y[k][2];
1088 }
1089 const double inv = 1.0 / static_cast<double>(nv);
1090 P.c[0] = sc[0] * inv;
1091 P.c[1] = sc[1] * inv;
1092 P.c[2] = sc[2] * inv;
1093 constexpr int ip[6] = {0, 1, 2, 0, 0, 1}, iq[6] = {0, 1, 2, 1, 2, 2};
1094 for (int k = 1; k + 1 < nv; ++k) {
1095 const double* A = Y[0];
1096 const double* B = Y[k];
1097 const double* C = Y[k + 1];
1098 const double u[3] = {B[0] - A[0], B[1] - A[1], B[2] - A[2]};
1099 const double w[3] = {C[0] - A[0], C[1] - A[1], C[2] - A[2]};
1100 const double cx = u[1] * w[2] - u[2] * w[1];
1101 const double cy = u[2] * w[0] - u[0] * w[2];
1102 const double cz = u[0] * w[1] - u[1] * w[0];
1103 const double At = 0.5 * Kokkos::sqrt(cx * cx + cy * cy + cz * cz);
1104 const double S[3] = {A[0] + B[0] + C[0], A[1] + B[1] + C[1], A[2] + B[2] + C[2]};
1105 P.a += At;
1106 for (int a = 0; a < 3; ++a)
1107 P.m1[a] += At * S[a] / 3.0;
1108 for (int q = 0; q < 6; ++q) {
1109 const int p0 = ip[q], q0 = iq[q];
1110 P.m2[q] += At / 12.0 * (A[p0] * A[q0] + B[p0] * B[q0] + C[p0] * C[q0] + S[p0] * S[q0]);
1111 }
1112 }
1113}
1114
1122 double t1[3], double t2[3], double org[3]) {
1123 if (!(P.n[0] * P.n[0] + P.n[1] * P.n[1] + P.n[2] * P.n[2] > 0.0))
1124 return false;
1125 nn[0] = P.n[0];
1126 nn[1] = P.n[1];
1127 nn[2] = P.n[2];
1128 curvFrame(nn, t1, t2);
1129 if (P.a > 0.0) {
1130 org[0] = P.m1[0] / P.a;
1131 org[1] = P.m1[1] / P.a;
1132 org[2] = P.m1[2] / P.a;
1133 } else {
1134 const double h[3] = {-0.5, -0.5, -0.5};
1135 g.toPhys(h, org);
1136 }
1137 return true;
1138}
1139
1154KOKKOS_INLINE_FUNCTION bool pvTermMoments(PvTerm& t, const PvMoments& P, const double off[3],
1155 const double org[3], const double t1[3],
1156 const double t2[3], const double nn[3], double dW,
1157 double cosMin, const VofMetric& g) {
1158 t.ok = false;
1159 const double cj = P.n[0] * nn[0] + P.n[1] * nn[1] + P.n[2] * nn[2];
1160 if (!(cj > cosMin)) // also rejects a degenerate normal (n = 0) whenever cosMin >= 0
1161 return false;
1162 if (!(P.a > 0.0))
1163 return false;
1164 double d[3];
1165 g.toPhys(off, d);
1166 d[0] -= org[0];
1167 d[1] -= org[1];
1168 d[2] -= org[2];
1169 const double rc[3] = {P.c[0] + d[0], P.c[1] + d[1], P.c[2] + d[2]};
1170 const double r = Kokkos::sqrt(rc[0] * rc[0] + rc[1] * rc[1] + rc[2] * rc[2]);
1171 const double w = wendlandWeight(r, dW);
1172 if (!(w > 0.0))
1173 return false;
1174 const double a = P.a;
1175 const double* m1 = P.m1;
1176 const double* m2 = P.m2;
1177 const double Q1[3] = {m1[0] + d[0] * a, m1[1] + d[1] * a, m1[2] + d[2] * a};
1178 // Q2 = m2 + d m1^T + m1 d^T + d d^T a
1179 const double Qxx = m2[0] + 2.0 * d[0] * m1[0] + d[0] * d[0] * a;
1180 const double Qyy = m2[1] + 2.0 * d[1] * m1[1] + d[1] * d[1] * a;
1181 const double Qzz = m2[2] + 2.0 * d[2] * m1[2] + d[2] * d[2] * a;
1182 const double Qxy = m2[3] + d[0] * m1[1] + m1[0] * d[1] + d[0] * d[1] * a;
1183 const double Qxz = m2[4] + d[0] * m1[2] + m1[0] * d[2] + d[0] * d[2] * a;
1184 const double Qyz = m2[5] + d[1] * m1[2] + m1[1] * d[2] + d[1] * d[2] * a;
1185 const double u[3] = {Qxx * t1[0] + Qxy * t1[1] + Qxz * t1[2],
1186 Qxy * t1[0] + Qyy * t1[1] + Qyz * t1[2],
1187 Qxz * t1[0] + Qyz * t1[1] + Qzz * t1[2]};
1188 const double v[3] = {Qxx * t2[0] + Qxy * t2[1] + Qxz * t2[2],
1189 Qxy * t2[0] + Qyy * t2[1] + Qyz * t2[2],
1190 Qxz * t2[0] + Qyz * t2[1] + Qzz * t2[2]};
1191 double s[6];
1192 s[0] = cj * a;
1193 s[1] = cj * (t1[0] * Q1[0] + t1[1] * Q1[1] + t1[2] * Q1[2]);
1194 s[2] = cj * (t2[0] * Q1[0] + t2[1] * Q1[1] + t2[2] * Q1[2]);
1195 s[3] = cj * (t1[0] * u[0] + t1[1] * u[1] + t1[2] * u[2]);
1196 s[4] = cj * (t2[0] * u[0] + t2[1] * u[1] + t2[2] * u[2]);
1197 s[5] = cj * (t2[0] * v[0] + t2[1] * v[1] + t2[2] * v[2]);
1198 if (!(Kokkos::fabs(s[0]) > 1e-14))
1199 return false;
1200 t.w = w;
1201 t.B = cj * (nn[0] * Q1[0] + nn[1] * Q1[1] + nn[2] * Q1[2]);
1202 for (int i = 0; i < 6; ++i)
1203 t.s[i] = s[i];
1204 t.ok = true;
1205 return true;
1206}
1207
1208} // namespace peclet::core::vof
1209
1210#endif // PECLET_CORE_VOF_CURVATURE_HPP
bool hfIsFull(double c, double eps)
Colour thresholds for "this cell is pure", at a caller-supplied tolerance.
constexpr int kPvEntries
Number of independent entries of the PV normal equations: the 21 of the lower triangle (with the diag...
void pvFitAccumLower(PvFit &f, const PvTerm &t)
Lower-triangle accumulation: pvFitAccum restricted to j <= i (design G §5.1c).
void pvPolygonBuild(double mx, double my, double mz, double alpha, PvPolygon &P)
Build a PvPolygon from a cell's PLIC plane: nv = plicPolygon(m, alpha, v), then vbar.
constexpr int kHfColumn
Column length of the height function, NH in Han et al.
bool pvOutsideSupport(const PvPolygon &P, const double off[3], const double org[3], double dW, const VofMetric &g)
true iff the cached polygon is CERTAINLY outside the Wendland support of a target with origin org (in...
void curvFrame(const double n[3], double t1[3], double t2[3])
Right-handed orthonormal frame (t1, t2, n) from a unit vector n.
bool pvFitTerm(PvTerm &t, double mx, double my, double mz, double alpha, const double off[3], const double org[3], const double t1[3], const double t2[3], const double nn[3], double dW, double cosMin, const VofMetric &g)
One stencil polygon's contribution to the PV fit: everything pvFitAdd computes before the accumulatio...
bool pvFrameMoments(const PvMoments &P, const VofMetric &g, double nn[3], double t1[3], double t2[3], double org[3])
Tier 3's fit frame from the TARGET's cached moments: nn = P.n, curvFrame(nn, t1, t2),...
bool ptFitSolve(const PtFit &f, double a[6], bool &red)
Solve the point fit, with the same rank cascade as pvFitSolve.
void pvFitInit(PvFit &f)
bool pvTermPolygon(PvTerm &t, const double np[3], const double v[8][3], int nv, const double off[3], const double org[3], const double t1[3], const double t2[3], const double nn[3], double dW, const VofMetric &g)
The POLYGON half of pvFitTerm (design G §5.1a): everything pvFitTerm does after if (nv < 3) return fa...
void pvMomentsBuild(double mx, double my, double mz, double alpha, const VofMetric &g, PvMoments &P)
Build a PvMoments from a cell's PLIC plane (index-space m, alpha) and the cell metric: the physical u...
void ptFitInit(PtFit &f)
constexpr double kHfPureEps
The FLOOR of the "this cell is pure" thresholds.
double paraboloidKappa(const double a[6])
Mean curvature (times two) of the paraboloid z = a0 + a1 x + a2 y + a3 x^2 + a4 x y + a5 y^2 at the o...
bool pvTermMoments(PvTerm &t, const PvMoments &P, const double off[3], const double org[3], const double t1[3], const double t2[3], const double nn[3], double dW, double cosMin, const VofMetric &g)
One stencil polygon's PV term from its cached moments (design G §5.1d, "V6"): the same integrals as p...
void pvFitAccum(PvFit &f, const PvTerm &t)
Fold one accepted term (t.ok) into the fit — the accumulation half of pvFitAdd, with its expressions ...
constexpr int kPvHalf
Half-width of the PV fit stencil: S = 2*kPvHalf + 1 = 5, Han et al.'s recommended S.
constexpr double kPtWeightWidth
Wendland support width for the tier-2b height-position fit.
CurvatureBranch
Which branch of the cascade produced a cell's curvature.
@ kCurvHfMixed
tier 2a: standard HF in one of the two remaining directions
@ kCurvNoEstimate
NO estimate: fewer than 3 usable PLIC polygons in the stencil.
@ kCurvPvReduced
tier 3 with the rank-deficient 3-parameter model (a1 = a2 = a4 = 0)
@ kCurvNone
not an interfacial cell — kappa is 0 because there is no interface
@ kCurvHfFit
tier 2b: paraboloid fit to the interface positions of whichever columns ARE consistent,...
@ kCurvPv
tier 3: PLIC-volumetric paraboloid fit, 5^3 Wendland-weighted
@ kCurvHf
tier 1: standard HF in the preferred (largest |n_d|) direction
int plicPolygon(double mx, double my, double mz, double alpha, double v[8][3])
Vertices of the polygon {x in [0,1]^3 : m .
bool pvTermNormal(double mx, double my, double mz, const double t1[3], const double t2[3], const double nn[3], double cosMin, const VofMetric &g, double np[3])
The NORMAL half of pvFitTerm (design G, flow doc/vof_curvature_cost_design.md §5.1a): the n2 > 0 chec...
bool pvFitSolve(const PvFit &f, double a[6], bool &red)
Solve the accumulated PV system.
double hfPatchKappa(const double h[9])
Mean-curvature-times-two from a 3x3 patch of heights, Han et al.
bool hfIsEmpty(double c, double eps)
void pvFitAccumEntry(double &a, const PvTerm &t, int e)
ONE entry e in [0, 27) of the accumulation, for an entry-parallel fold (design G §5....
constexpr double kPvWeightWidth
Wendland support width d in cell units.
void ptFitAdd(PtFit &f, const double X[3], const double org[3], const double t1[3], const double t2[3], const double nn[3], double dW, const VofMetric &g)
Fold one interface point into the fit.
double vofPhysNormalInv(const double m[3], const VofMetric &g, double n[3])
vofPhysNormal in the RECIPROCAL-MULTIPLY form — n = (H^-1 m) * (1/|H^-1 m|), which is what the curvat...
Definition plic.hpp:160
void polygonMoments2d(const double xy[8][2], int nv, double s[6])
The six monomial integrals s = ( |G|, int x, int y, int x^2, int xy, int y^2 ) over the planar polygo...
bool curvSolveSym(const double Ain[6][6], const double bin[6], int n, double x[6], double pivTol)
Solve the symmetric positive-semidefinite n x n system A x = b (n <= 6) by LDL^T with Jacobi (diagona...
bool pvFitAdd(PvFit &f, double mx, double my, double mz, double alpha, const double off[3], const double org[3], const double t1[3], const double t2[3], const double nn[3], double dW, double cosMin, const VofMetric &g)
Fold one stencil cell's PLIC polygon into the fit: pvFitTerm, then pvFitAccum if the polygon is accep...
bool hfColumnHeight(const double *col, int nh, double &h, int &orient, double monoTol, double pureEps=kHfPureEps)
One height from one column of colour values.
void polygonAreaCentroid(const double v[8][3], int nv, double ctr[3], double &area)
Area and centroid of a planar polygon in 3-D (fan triangulation from v[0]).
double wendlandWeight(double r, double d)
Wendland C2 radial basis function, Han et al.
void pvFitFromEntries(PvFit &f, const double ent[kPvEntries], int npoly)
A PvFit from the 27 entries of pvFitAccumEntry (+ npoly); the upper triangle is zero.
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
PECLET_HD Vec3< Real > cross(Vec3< Real > a, Vec3< Real > b)
Definition portable.hpp:74
core — PLIC (piecewise-linear interface construction) geometric toolbox: the analytic plane<->volume ...
Accumulator for an ordinary weighted linear least-squares paraboloid through points.
Accumulator for the PV normal equations, Han et al.
The per-cell MOMENT cache entry (design G §5.1d, "V6"): the 3-D area moments of a cell's PLIC polygon...
double m2[6]
int_P Y Y^T dA, in the order xx, yy, zz, xy, xz, yz
double n[3]
unit PHYSICAL normal (vofPhysNormalInv); (0,0,0) if degenerate
double c[3]
physical vertex average (1/nv) sum_k Y_k — the Wendland point
double a
area (physical); 0 when nv < 3 or the normal is degenerate
The per-cell POLYGON cache entry (design G §5.1b, "V5"): plicPolygon's output for one cell's PLIC pla...
double v[8][3]
cell-local [0,1]^3 vertices; slots >= nv unused
int nv
plicPolygon's return value (0 if the normal is degenerate)
double vbar[3]
(sum_k v[k]) * (1/nv) - 0.5; 0 when nv == 0
One stencil cell's PLIC polygon in the PV fit: the parameters of pvFitTerm and pvFitAdd.
The per-axis cell size, in the solver's reference length (hRef), of the cell these kernels map to the...
Definition plic.hpp:108
void toPhys(const double X[3], double out[3]) const
Index displacement -> physical displacement (in hRef): ‘X’_a = h[a] X_a`.
Definition plic.hpp:122