core 1.5.0
Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)
Loading...
Searching...
No Matches
cut_cell_geometry.hpp
Go to the documentation of this file.
1
35#ifndef PECLET_CORE_SCHEME_CUT_CELL_GEOMETRY_HPP
36#define PECLET_CORE_SCHEME_CUT_CELL_GEOMETRY_HPP
37
38#include <cmath>
39
40#include "peclet/core/scheme/cut_cell_closure.hpp" // PECLET_CORE_CC_HD
41
42namespace peclet::core::scheme {
43
46inline constexpr double kApertureSnap = 1e-3;
48inline constexpr double kPieceDropRel = 1e-14;
50inline constexpr double kSingleFacetRho = 0.5;
51
53enum class CutCellKind : int {
54 none = 0,
55 single = 1,
56 gap = 2,
57 thinSolid = 3,
58};
59
62 double kappa;
63 double aperture[6];
64 double areaPL[3];
65 double areaSum;
66 int nFacet;
67 double facetArea[2][3];
68 double facetCentroid[2][3];
70};
71
72namespace detail {
73
74PECLET_CORE_CC_HD double ccgSqrt(double x) {
75#ifdef KOKKOS_INLINE_FUNCTION
76 return Kokkos::sqrt(x);
77#else
78 return std::sqrt(x);
79#endif
80}
81
82PECLET_CORE_CC_HD double ccgAbs(double x) {
83 return x < 0.0 ? -x : x;
84}
85
86PECLET_CORE_CC_HD double ccgMaxAbs3(double a, double b, double c) {
87 double m = ccgAbs(a);
88 m = ccgAbs(b) > m ? ccgAbs(b) : m;
89 return ccgAbs(c) > m ? ccgAbs(c) : m;
90}
91
92PECLET_CORE_CC_HD double ccgMaxAbs4(double a, double b, double c, double d) {
93 const double m = ccgMaxAbs3(a, b, c);
94 return ccgAbs(d) > m ? ccgAbs(d) : m;
95}
96
97// Triangle, one positive vertex x > 0 >= y, z: F = x^2 / ((x - y)(x - z)).
98PECLET_CORE_CC_HD double triOnePositive(double x, double y, double z) {
99 const double den = (x - y) * (x - z);
100 if (den > 0.0)
101 return (x * x) / den;
102 // Underflow only (every |value| below ~1e-154): F is scale-invariant, so rescale once.
103 const double s = 1.0 / ccgMaxAbs3(x, y, z);
104 x *= s;
105 y *= s;
106 z *= s;
107 return (x * x) / ((x - y) * (x - z));
108}
109
110// Tetrahedron, one positive vertex a > 0 >= b1, b2, b3: F = a^3 / ((a-b1)(a-b2)(a-b3)).
111PECLET_CORE_CC_HD double tetOnePositive(double a, double b1, double b2, double b3) {
112 const double den = ((a - b1) * (a - b2)) * (a - b3);
113 if (den > 0.0)
114 return (a * a * a) / den;
115 const double s = 1.0 / ccgMaxAbs4(a, b1, b2, b3); // underflow only; F is scale-invariant
116 a *= s;
117 b1 *= s;
118 b2 *= s;
119 b3 *= s;
120 return (a * a * a) / (((a - b1) * (a - b2)) * (a - b3));
121}
122
123// Tetrahedron, two positive vertices a1, a2 > 0 >= b1, b2 (§2.2; every term non-negative):
124// F = [a1²a2² − (a1+a2)(b1+b2)a1a2 + b1b2(a1²+a1a2+a2²)] / [(a1−b1)(a1−b2)(a2−b1)(a2−b2)].
125PECLET_CORE_CC_HD double tetTwoPositive(double a1, double a2, double b1, double b2) {
126 double den = ((a1 - b1) * (a1 - b2)) * ((a2 - b1) * (a2 - b2));
127 if (!(den > 0.0)) {
128 const double s = 1.0 / ccgMaxAbs4(a1, a2, b1, b2); // underflow only; F is scale-invariant
129 a1 *= s;
130 a2 *= s;
131 b1 *= s;
132 b2 *= s;
133 den = ((a1 - b1) * (a1 - b2)) * ((a2 - b1) * (a2 - b2));
134 }
135 const double p = a1 * a2;
136 const double num = p * p - (a1 + a2) * (b1 + b2) * p + b1 * b2 * (a1 * a1 + p + a2 * a2);
137 return num / den;
138}
139
140} // namespace detail
141
145PECLET_CORE_CC_HD double triPositiveFraction(double a, double b, double c) {
146 const bool pa = a > 0.0, pb = b > 0.0, pc = c > 0.0;
147 const int np = (pa ? 1 : 0) + (pb ? 1 : 0) + (pc ? 1 : 0);
148 if (np == 3)
149 return 1.0;
150 if (np == 0)
151 return 0.0;
152 if (np == 1) { // rotate the positive vertex to the front
153 if (pa)
154 return detail::triOnePositive(a, b, c);
155 if (pb)
156 return detail::triOnePositive(b, c, a);
157 return detail::triOnePositive(c, a, b);
158 }
159 // np == 2: the fraction of the single non-positive vertex's mirror, F = 1 − b²/((b−a1)(b−a2))
160 // = 1 − triOnePositive(−b, −a1, −a2) (the negative part as a one-positive triangle of −φ).
161 if (!pa)
162 return 1.0 - detail::triOnePositive(-a, -b, -c);
163 if (!pb)
164 return 1.0 - detail::triOnePositive(-b, -c, -a);
165 return 1.0 - detail::triOnePositive(-c, -a, -b);
166}
167
170PECLET_CORE_CC_HD double tetPositiveFraction(double v0, double v1, double v2, double v3) {
171 const double v[4] = {v0, v1, v2, v3};
172 double pos[4], neg[4];
173 int np = 0, nn = 0;
174 for (int k = 0; k < 4; ++k) {
175 if (v[k] > 0.0)
176 pos[np++] = v[k];
177 else
178 neg[nn++] = v[k];
179 }
180 if (np == 0)
181 return 0.0;
182 if (np == 4)
183 return 1.0;
184 if (np == 1)
185 return detail::tetOnePositive(pos[0], neg[0], neg[1], neg[2]);
186 if (np == 3) // 1 − b³/((b−a1)(b−a2)(b−a3)) = 1 − tetOnePositive(−b, −a1, −a2, −a3)
187 return 1.0 - detail::tetOnePositive(-neg[0], -pos[0], -pos[1], -pos[2]);
188 return detail::tetTwoPositive(pos[0], pos[1], neg[0], neg[1]);
189}
190
196PECLET_CORE_CC_HD double fanFaceAperture(double v00, double v10, double v11, double v01,
197 double cc) {
198 return 0.25 * (triPositiveFraction(v00, v10, cc) + triPositiveFraction(v10, v11, cc) +
200}
201
204 if (a < kApertureSnap)
205 return 0.0;
206 if (a > 1.0 - kApertureSnap)
207 return 1.0;
208 return a;
209}
210
212PECLET_CORE_CC_HD double cutCellFaceArea(int a, const double h[3]) {
213 return a == 0 ? h[1] * h[2] : (a == 1 ? h[0] * h[2] : h[0] * h[1]);
214}
215
219PECLET_CORE_CC_HD void facetAreaVector(const double ap[6], const double h[3], double out[3]) {
220 for (int a = 0; a < 3; ++a)
221 out[a] = cutCellFaceArea(a, h) * (ap[2 * a + 1] - ap[2 * a]);
222}
223
224namespace detail {
225
226// Position of corner c (bits x, y, z = bit 0, 1, 2) relative to the cell centre.
227PECLET_CORE_CC_HD void ccgCornerPos(int c, const double h[3], double x[3]) {
228 for (int a = 0; a < 3; ++a)
229 x[a] = (((c >> a) & 1) ? 0.5 : -0.5) * h[a];
230}
231
232// The two tangent axes of a face of axis a, ascending (the ccFaceOpenMS t1, t2).
233PECLET_CORE_CC_HD void ccgTangents(int a, int& t1, int& t2) {
234 t1 = (a == 0) ? 1 : 0;
235 t2 = (a == 2) ? 1 : 2;
236}
237
238// Corner k (0..3) of face f's fan loop: c00, c10, c11, c01 in the (t1, t2) bits.
240 const int a = f >> 1, side = f & 1;
241 int t1, t2;
242 ccgTangents(a, t1, t2);
243 const int u = (k == 1 || k == 2) ? 1 : 0;
244 const int w = (k >= 2) ? 1 : 0;
245 return (side << a) | (u << t1) | (w << t2);
246}
247
248// Tetrahedron (f, k): face f's fan triangle k = (corner k, corner k+1, face centre) coned to the
249// cell centre. Vertex values and positions in that order.
250PECLET_CORE_CC_HD void ccgFanTet(int f, int k, const double corner[8], const double face[6],
251 double centre, const double h[3], double val[4],
252 double pos[4][3]) {
253 const int c0 = ccgFanCorner(f, k), c1 = ccgFanCorner(f, (k + 1) & 3);
254 val[0] = corner[c0];
255 val[1] = corner[c1];
256 val[2] = face[f];
257 val[3] = centre;
258 ccgCornerPos(c0, h, pos[0]);
259 ccgCornerPos(c1, h, pos[1]);
260 const int a = f >> 1;
261 for (int d = 0; d < 3; ++d) {
262 pos[2][d] = 0.0;
263 pos[3][d] = 0.0;
264 }
265 pos[2][a] = ((f & 1) ? 0.5 : -0.5) * h[a];
266}
267
268// Edge crossing x = x_p + t (x_n − x_p), t = φ_p / (φ_p − φ_n) ∈ (0, 1].
269PECLET_CORE_CC_HD void ccgCross(double vp, double vn, const double xp[3], const double xn[3],
270 double x[3]) {
271 const double t = vp / (vp - vn);
272 for (int d = 0; d < 3; ++d)
273 x[d] = xp[d] + t * (xn[d] - xp[d]);
274}
275
276PECLET_CORE_CC_HD void ccgCrossProd(const double u[3], const double v[3], double w[3]) {
277 w[0] = u[1] * v[2] - u[2] * v[1];
278 w[1] = u[2] * v[0] - u[0] * v[2];
279 w[2] = u[0] * v[1] - u[1] * v[0];
280}
281
282PECLET_CORE_CC_HD double ccgNorm(const double v[3]) {
283 return ccgSqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
284}
285
286// The zero-set piece of one tetrahedron with 0 < n+ < 4 (§2.2): area vector `vec` oriented toward
287// a positive vertex, its magnitude `area`, and centroid `cen`. Returns false when the tet is not
288// cut (n+ = 0 or 4).
289PECLET_CORE_CC_HD bool ccgTetPiece(const double val[4], const double pos[4][3], double vec[3],
290 double& area, double cen[3]) {
291 int ip[4], in[4];
292 int np = 0, nn = 0;
293 for (int k = 0; k < 4; ++k) {
294 if (val[k] > 0.0)
295 ip[np++] = k;
296 else
297 in[nn++] = k;
298 }
299 if (np == 0 || np == 4)
300 return false;
301 const int pRef = ip[0]; // the positive vertex the area vector is oriented toward
302 double X[4][3];
303 if (np == 2) {
304 // Quad X11, X12, X22, X21 (X_ij on edge p_i – n_j); area ½ (X2 − X0) × (X3 − X1).
305 ccgCross(val[ip[0]], val[in[0]], pos[ip[0]], pos[in[0]], X[0]);
306 ccgCross(val[ip[0]], val[in[1]], pos[ip[0]], pos[in[1]], X[1]);
307 ccgCross(val[ip[1]], val[in[1]], pos[ip[1]], pos[in[1]], X[2]);
308 ccgCross(val[ip[1]], val[in[0]], pos[ip[1]], pos[in[0]], X[3]);
309 double d02[3], d13[3];
310 for (int d = 0; d < 3; ++d) {
311 d02[d] = X[2][d] - X[0][d];
312 d13[d] = X[3][d] - X[1][d];
313 }
315 for (int d = 0; d < 3; ++d)
316 vec[d] *= 0.5;
317 // Centroid: area-weighted mean of the triangles (X0, X1, X2) and (X0, X2, X3).
318 double d01[3], d03[3], tA[3], tB[3];
319 for (int d = 0; d < 3; ++d) {
320 d01[d] = X[1][d] - X[0][d];
321 d03[d] = X[3][d] - X[0][d];
322 }
325 const double wA = 0.5 * ccgNorm(tA), wB = 0.5 * ccgNorm(tB);
326 const double wS = wA + wB;
327 for (int d = 0; d < 3; ++d) {
328 const double cA = (X[0][d] + X[1][d] + X[2][d]) / 3.0;
329 const double cB = (X[0][d] + X[2][d] + X[3][d]) / 3.0;
330 cen[d] = wS > 0.0 ? (wA * cA + wB * cB) / wS : 0.25 * (X[0][d] + X[1][d] + X[2][d] + X[3][d]);
331 }
332 } else {
333 // Triangle: n+ = 1 → crossings on (p, n1), (p, n2), (p, n3); n+ = 3 → on (p1, n), (p2, n),
334 // (p3, n). Area ½ (X1 − X0) × (X2 − X0).
335 for (int k = 0; k < 3; ++k) {
336 const int p = (np == 1) ? ip[0] : ip[k];
337 const int n = (np == 1) ? in[k] : in[0];
338 ccgCross(val[p], val[n], pos[p], pos[n], X[k]);
339 }
340 double d01[3], d02[3];
341 for (int d = 0; d < 3; ++d) {
342 d01[d] = X[1][d] - X[0][d];
343 d02[d] = X[2][d] - X[0][d];
344 }
346 for (int d = 0; d < 3; ++d) {
347 vec[d] *= 0.5;
348 cen[d] = (X[0][d] + X[1][d] + X[2][d]) / 3.0;
349 }
350 }
351 // Orient toward the positive vertex.
352 const double dot = vec[0] * (pos[pRef][0] - X[0][0]) + vec[1] * (pos[pRef][1] - X[0][1]) +
353 vec[2] * (pos[pRef][2] - X[0][2]);
354 if (dot < 0.0)
355 for (int d = 0; d < 3; ++d)
356 vec[d] = -vec[d];
357 area = ccgNorm(vec);
358 return true;
359}
360
361} // namespace detail
362
374 const double face[6], double centre,
375 const double h[3]) {
377 g.kappa = 0.0;
378 g.areaSum = 0.0;
379 g.nFacet = 0;
381 for (int d = 0; d < 3; ++d) {
382 g.areaPL[d] = 0.0;
383 g.facetArea[0][d] = g.facetArea[1][d] = 0.0;
384 g.facetCentroid[0][d] = g.facetCentroid[1][d] = 0.0;
385 }
386
387 // Apertures: face f's fan (c00, c10, cc), (c10, c11, cc), (c11, c01, cc), (c01, c00, cc),
388 // summed in that order — the same triangles and order from both cells sharing the face.
389 for (int f = 0; f < 6; ++f) {
390 const double cc = face[f];
391 const double v00 = corner[detail::ccgFanCorner(f, 0)];
392 const double v10 = corner[detail::ccgFanCorner(f, 1)];
393 const double v11 = corner[detail::ccgFanCorner(f, 2)];
394 const double v01 = corner[detail::ccgFanCorner(f, 3)];
396 }
397
398 // Uniform sign over all 15 samples: κ = 1 or 0, no facet (the apertures above are then exactly
399 // 1 or 0 as well).
400 bool anyPos = centre > 0.0, anyNonPos = !(centre > 0.0);
401 for (int c = 0; c < 8; ++c) {
402 anyPos = anyPos || corner[c] > 0.0;
403 anyNonPos = anyNonPos || !(corner[c] > 0.0);
404 }
405 for (int f = 0; f < 6; ++f) {
406 anyPos = anyPos || face[f] > 0.0;
407 anyNonPos = anyNonPos || !(face[f] > 0.0);
408 }
409 if (!anyPos || !anyNonPos) {
410 g.kappa = anyPos ? 1.0 : 0.0;
411 return g;
412 }
413
414 double minA = cutCellFaceArea(0, h);
415 minA = cutCellFaceArea(1, h) < minA ? cutCellFaceArea(1, h) : minA;
416 minA = cutCellFaceArea(2, h) < minA ? cutCellFaceArea(2, h) : minA;
417 const double dropArea = kPieceDropRel * minA;
418
419 // Pass 1: κ, the net area vector, Σ|piece|, the weighted centroid and the largest piece.
420 double ksum = 0.0;
421 double wc[3] = {0.0, 0.0, 0.0};
422 double refVec[3] = {0.0, 0.0, 0.0};
423 double refArea = -1.0;
424 for (int f = 0; f < 6; ++f) {
425 for (int k = 0; k < 4; ++k) {
426 double val[4], pos[4][3];
427 detail::ccgFanTet(f, k, corner, face, centre, h, val, pos);
428 ksum += tetPositiveFraction(val[0], val[1], val[2], val[3]);
429 double vec[3], cen[3], area;
430 if (!detail::ccgTetPiece(val, pos, vec, area, cen) || area < dropArea)
431 continue;
432 for (int d = 0; d < 3; ++d) {
433 g.areaPL[d] += vec[d];
434 wc[d] += area * cen[d];
435 }
436 g.areaSum += area;
437 if (area > refArea) {
438 refArea = area;
439 for (int d = 0; d < 3; ++d)
440 refVec[d] = vec[d];
441 }
442 }
443 }
444 g.kappa = ksum / 24.0;
445 if (!(g.areaSum > 0.0))
446 return g; // every piece below the drop threshold: no facet
447
448 const double rho = detail::ccgNorm(g.areaPL) / g.areaSum;
449 if (rho < kSingleFacetRho) {
450 // Pass 2: split the pieces into the reference group A (n·n_ref >= 0) and B.
451 double vA[3] = {0.0, 0.0, 0.0}, vB[3] = {0.0, 0.0, 0.0};
452 double cA[3] = {0.0, 0.0, 0.0}, cB[3] = {0.0, 0.0, 0.0};
453 double sA = 0.0, sB = 0.0;
454 for (int f = 0; f < 6; ++f) {
455 for (int k = 0; k < 4; ++k) {
456 double val[4], pos[4][3];
457 detail::ccgFanTet(f, k, corner, face, centre, h, val, pos);
458 double vec[3], cen[3], area;
459 if (!detail::ccgTetPiece(val, pos, vec, area, cen) || area < dropArea)
460 continue;
461 const double dot = vec[0] * refVec[0] + vec[1] * refVec[1] + vec[2] * refVec[2];
462 double* v = dot >= 0.0 ? vA : vB;
463 double* c = dot >= 0.0 ? cA : cB;
464 for (int d = 0; d < 3; ++d) {
465 v[d] += vec[d];
466 c[d] += area * cen[d];
467 }
468 (dot >= 0.0 ? sA : sB) += area;
469 }
470 }
471 if (sA > 0.0 && sB > 0.0) {
472 for (int d = 0; d < 3; ++d) {
473 g.facetArea[0][d] = vA[d];
474 g.facetArea[1][d] = vB[d];
475 g.facetCentroid[0][d] = cA[d] / sA;
476 g.facetCentroid[1][d] = cB[d] / sB;
477 }
478 const double sep = (g.facetCentroid[1][0] - g.facetCentroid[0][0]) * vA[0] +
479 (g.facetCentroid[1][1] - g.facetCentroid[0][1]) * vA[1] +
480 (g.facetCentroid[1][2] - g.facetCentroid[0][2]) * vA[2];
481 g.nFacet = 2;
483 return g;
484 }
485 }
486 g.nFacet = 1;
488 for (int d = 0; d < 3; ++d) {
489 g.facetArea[0][d] = g.areaPL[d];
490 g.facetCentroid[0][d] = wc[d] / g.areaSum;
491 }
492 return g;
493}
494
495} // namespace peclet::core::scheme
496
497#endif // PECLET_CORE_SCHEME_CUT_CELL_GEOMETRY_HPP
core — the shared cut-cell IBM boundary-distance closure polynomials (flow <-> amr).
#define PECLET_CORE_CC_HD
PECLET_CORE_CC_HD double triOnePositive(double x, double y, double z)
PECLET_CORE_CC_HD void ccgFanTet(int f, int k, const double corner[8], const double face[6], double centre, const double h[3], double val[4], double pos[4][3])
PECLET_CORE_CC_HD void ccgCrossProd(const double u[3], const double v[3], double w[3])
PECLET_CORE_CC_HD double ccgSqrt(double x)
PECLET_CORE_CC_HD void ccgTangents(int a, int &t1, int &t2)
PECLET_CORE_CC_HD double ccgMaxAbs4(double a, double b, double c, double d)
PECLET_CORE_CC_HD double ccgAbs(double x)
PECLET_CORE_CC_HD double tetOnePositive(double a, double b1, double b2, double b3)
PECLET_CORE_CC_HD void ccgCornerPos(int c, const double h[3], double x[3])
PECLET_CORE_CC_HD bool ccgTetPiece(const double val[4], const double pos[4][3], double vec[3], double &area, double cen[3])
PECLET_CORE_CC_HD double ccgNorm(const double v[3])
PECLET_CORE_CC_HD void ccgCross(double vp, double vn, const double xp[3], const double xn[3], double x[3])
PECLET_CORE_CC_HD int ccgFanCorner(int f, int k)
PECLET_CORE_CC_HD double ccgMaxAbs3(double a, double b, double c)
PECLET_CORE_CC_HD double tetTwoPositive(double a1, double a2, double b1, double b2)
PECLET_CORE_CC_HD CutCellGeometry cutCellGeometryFanTet(const double corner[8], const double face[6], double centre, const double h[3])
The cut-cell record of one cell from its 15 PL samples (§2.2): corner[8] (index bx+2by+4bz),...
constexpr double kSingleFacetRho
ρ = |areaPL| / areaSum at or above which the cell carries ONE facet (§2.2).
PECLET_CORE_CC_HD double fanFaceAperture(double v00, double v10, double v11, double v01, double cc)
PL aperture of one face from its fan (§2.2): the corner values in loop order c00, c10,...
PECLET_CORE_CC_HD double cutCellFaceArea(int a, const double h[3])
Face area of axis a for cell size h: the product of the two other spacings.
CutCellKind
Facet classification of a cut cell (§2.2).
@ none
no facet (uncut, or every piece below the drop threshold)
@ thinSolid
two facets, solid between two fluids (a counted resolution error)
@ single
one facet (ρ ≥ 0.5, or the two-group split left a group empty)
@ gap
two facets, fluid between two walls
PECLET_CORE_CC_HD double triPositiveFraction(double a, double b, double c)
Fraction of a triangle on which the linear interpolant of its vertex values (a, b,...
constexpr double kApertureSnap
Aperture snap threshold at BOTH ends (§2.3): a < 1e-3 → 0, a > 1 − 1e-3 → 1.
constexpr double kPieceDropRel
A facet piece with area < kPieceDropRel · min_a A_a is dropped (§2.2).
PECLET_CORE_CC_HD double snapAperture(double a)
Snap a face aperture at both ends (§2.3): a < 1e-3 → 0, a > 1 − 1e-3 → 1, else a.
PECLET_CORE_CC_HD void facetAreaVector(const double ap[6], const double h[3], double out[3])
The area vector the apertures imply through the divergence theorem (§2.3): out_a = A_a (ap[2a+1] − ap...
PECLET_CORE_CC_HD double tetPositiveFraction(double v0, double v1, double v2, double v3)
Fraction of a tetrahedron on which the linear interpolant of its vertex values is strictly positive (...
PECLET_HD Real dot(Vec3< Real > a, Vec3< Real > b)
Definition portable.hpp:70
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
The geometric record of one cell (§2.1). Double, cell-centred frame, internal lengths.
double facetCentroid[2][3]
per facet: centroid relative to the cell centre; unused 0
double areaPL[3]
net PL facet area vector (into the fluid)
double kappa
fluid volume fraction ∈ [0, 1]
double aperture[6]
open fraction of each face, −x,+x,−y,+y,−z,+z, UNSNAPPED
double facetArea[2][3]
per facet: area vector (into the fluid); unused slots 0