DeviceSdf gains `tip_corner: Option<f64>` (None = the capsule, as before; Some(r_c) = a FLAT tip through the centreline's last point, normal to the last segment, corners rounded to r_c): the last segment becomes a ray for the lateral distance and the strip is cut by the tip plane with the span cut's rounded intersection. Host twin (plate.rs: closest/tip_axial/ flat_cap, polyline and plate bodies) and the device φ and velocity (e3_geom.cu geom_phi_at / body_velocity / plate_dist; GeomSdf flat_tip + tip_corner) expression for expression. Knobs: flag test RTX_E3_FLAG_TIP=flat + RTX_E3_FLAG_TIP_CORNER (default 0.00125 m; the tip inset defaults to 0 with the flat tip; the host φ is the device form's); R8-a harness RTX_E3FSI_TIP=flat + RTX_E3FSI_TIP_CORNER (the centreline gains node A as a 36th station). New host test embedded3_flat_tip (G2 geometry: tip plane at the last point, r_c = half = the capsule pulled back by half to 4e-17, cut volume and wall area vs the analytic rounded rectangle at ny 62/124/248). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
577 lines
24 KiB
Plaintext
577 lines
24 KiB
Plaintext
/**
|
||
* embedded3 R6-1: the body's φ and the cut geometry on the device — the
|
||
* host `cut.rs::CutGeometry::build_from` expression for expression in fp64
|
||
* (compiled with FMA contraction off; `/` and `sqrt` are IEEE round-to-
|
||
* nearest in double on the device, as on the host), and the flag test's
|
||
* φ (`tests/embedded3_flag_wake.rs`: the circle, the capsule around the
|
||
* centreline polyline, the span cut with rounded edges, the fillet union).
|
||
*
|
||
* Kernels:
|
||
* e3_geom_phi per corner: the narrow band's keep-or-evaluate (bound −
|
||
* motion > band keeps φ), φ from the polyline otherwise;
|
||
* e3_geom_faces per face of one component: the aperture by the two
|
||
* Kuhn triangles and the face-centre φ, where a corner was
|
||
* evaluated (every face on a full pass);
|
||
* e3_geom_cells per cell: the fluid volume by the six Kuhn tetrahedra
|
||
* and the wall vector by closure of the face apertures;
|
||
* e3_geom_gather compact copies for the host mirror.
|
||
*/
|
||
|
||
struct GeomSdf {
|
||
double cx, cy, rc; /* the circle */
|
||
double zc, span, r_edge;/* the span cut */
|
||
double half; /* capsule half-thickness */
|
||
double fillet; /* root fillet radius (0: min) */
|
||
int cyl_cut, flag_cut; /* cut to the span */
|
||
int npts; /* polyline points (x, y interleaved) */
|
||
int nst, ns; /* R8-c plate: stations and points per station (nst 0: the polyline) */
|
||
int flat_tip; /* R8-h: 1 = the flat tip (corners rounded to tip_corner); 0 = the capsule */
|
||
double tip_corner;
|
||
};
|
||
|
||
struct GeomGrid {
|
||
int nx, ny, nz;
|
||
double dx, dy, dz;
|
||
};
|
||
|
||
__device__ __forceinline__ double rs_min(double a, double b) { return b < a ? b : a; }
|
||
__device__ __forceinline__ double rs_max(double a, double b) { return b > a ? b : a; }
|
||
|
||
/* The span cut with rounded edges (flag_3d / cylinder_3d). */
|
||
__device__ __forceinline__ double span_cut(double d2, double z, const GeomSdf& s)
|
||
{
|
||
double r = s.r_edge;
|
||
double q1 = d2 + r;
|
||
double q2 = fabs(z - s.zc) - 0.5 * s.span + r;
|
||
double m1 = rs_max(q1, 0.0), m2 = rs_max(q2, 0.0);
|
||
double outside = sqrt(m1 * m1 + m2 * m2);
|
||
return outside + rs_min(rs_max(q1, q2), 0.0) - r;
|
||
}
|
||
|
||
/* R8-h: the tip's axial distance along the last segment a→b from b (plate.rs tip_axial). */
|
||
__device__ __forceinline__ double tip_axial(double x, double y, double ax, double ay, double bx, double by)
|
||
{
|
||
double ex = bx - ax, ey = by - ay;
|
||
double l = sqrt(ex * ex + ey * ey);
|
||
return ((x - bx) * ex + (y - by) * ey) / l;
|
||
}
|
||
|
||
/* R8-h: the flat tip's rounded cap (plate.rs flat_cap). */
|
||
__device__ __forceinline__ double flat_cap(double dl, double a, double r)
|
||
{
|
||
double q1 = dl + r;
|
||
double q2 = a + r;
|
||
double m1 = rs_max(q1, 0.0), m2 = rs_max(q2, 0.0);
|
||
double outside = sqrt(m1 * m1 + m2 * m2);
|
||
return outside + rs_min(rs_max(q1, q2), 0.0) - r;
|
||
}
|
||
|
||
/*
|
||
* R8-c: the deformed plate (`plate.rs`, the host twin expression for
|
||
* expression): `poly` holds the stations' rows (x, y interleaved, row-major
|
||
* by station) followed by the stations' z. The stations bracketing z are
|
||
* interpolated linearly into one polyline (outside their range: the end
|
||
* station as it is), the in-plane closest point taken as the polyline's,
|
||
* the distance corrected for the spanwise slope d / sqrt(1 + (e·c)²);
|
||
* with `vel`, the velocity at the closest point the same way.
|
||
*/
|
||
__device__ double plate_dist(double x, double y, double z, const GeomSdf& s,
|
||
const double* __restrict__ P, const double* __restrict__ V,
|
||
double* vx, double* vy, double* axial)
|
||
{
|
||
int nst = s.nst, ns = s.ns;
|
||
const double* Z = P + 2 * (long long) nst * ns;
|
||
int k = 0, interp = 0;
|
||
double w = 0.0;
|
||
if (nst > 1) {
|
||
if (z <= Z[0]) {
|
||
k = 0;
|
||
} else if (z >= Z[nst - 1]) {
|
||
k = nst - 2;
|
||
} else {
|
||
while (k + 1 < nst && Z[k + 1] <= z) ++k;
|
||
if (k > nst - 2) k = nst - 2;
|
||
}
|
||
interp = 1;
|
||
w = (z - Z[k]) / (Z[k + 1] - Z[k]);
|
||
/* beyond the end stations: at most half an interval extrapolated */
|
||
if (w < -0.5) w = -0.5;
|
||
if (w > 1.5) w = 1.5;
|
||
}
|
||
const double* R0 = P + 2 * (long long) k * ns;
|
||
const double* R1 = interp ? R0 + 2 * ns : R0;
|
||
double best = 1.0 / 0.0, ub = 0.0, qbx = 0.0, qby = 0.0;
|
||
int mb = 0;
|
||
for (int m = 0; m + 1 < ns; ++m) {
|
||
double ax, ay, bx, by;
|
||
if (interp) {
|
||
ax = R0[2 * m] + w * (R1[2 * m] - R0[2 * m]);
|
||
ay = R0[2 * m + 1] + w * (R1[2 * m + 1] - R0[2 * m + 1]);
|
||
bx = R0[2 * m + 2] + w * (R1[2 * m + 2] - R0[2 * m + 2]);
|
||
by = R0[2 * m + 3] + w * (R1[2 * m + 3] - R0[2 * m + 3]);
|
||
} else {
|
||
ax = R0[2 * m]; ay = R0[2 * m + 1];
|
||
bx = R0[2 * m + 2]; by = R0[2 * m + 3];
|
||
}
|
||
double ex = bx - ax, ey = by - ay;
|
||
double l2 = ex * ex + ey * ey;
|
||
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
|
||
if (u < 0.0) u = 0.0;
|
||
if (u > 1.0 && !(s.flat_tip && m + 2 == ns)) u = 1.0;
|
||
double px = ax + u * ex, py = ay + u * ey;
|
||
double qx = x - px, qy = y - py;
|
||
double d = sqrt(qx * qx + qy * qy);
|
||
if (d < best) {
|
||
best = d;
|
||
mb = m;
|
||
ub = u > 1.0 ? 1.0 : u;
|
||
qbx = qx;
|
||
qby = qy;
|
||
}
|
||
}
|
||
if (interp && best > 0.0) {
|
||
double dz = Z[k + 1] - Z[k];
|
||
double cax = R1[2 * mb] - R0[2 * mb], cay = R1[2 * mb + 1] - R0[2 * mb + 1];
|
||
double cbx = R1[2 * mb + 2] - R0[2 * mb + 2], cby = R1[2 * mb + 3] - R0[2 * mb + 3];
|
||
double cx = (cax + ub * (cbx - cax)) / dz;
|
||
double cy = (cay + ub * (cby - cay)) / dz;
|
||
double qn = (qbx * cx + qby * cy) / best;
|
||
best = best / sqrt(1.0 + qn * qn);
|
||
}
|
||
if (s.flat_tip && axial) {
|
||
int m = ns - 2;
|
||
double ax, ay, bx, by;
|
||
if (interp) {
|
||
ax = R0[2 * m] + w * (R1[2 * m] - R0[2 * m]);
|
||
ay = R0[2 * m + 1] + w * (R1[2 * m + 1] - R0[2 * m + 1]);
|
||
bx = R0[2 * m + 2] + w * (R1[2 * m + 2] - R0[2 * m + 2]);
|
||
by = R0[2 * m + 3] + w * (R1[2 * m + 3] - R0[2 * m + 3]);
|
||
} else {
|
||
ax = R0[2 * m]; ay = R0[2 * m + 1];
|
||
bx = R0[2 * m + 2]; by = R0[2 * m + 3];
|
||
}
|
||
*axial = tip_axial(x, y, ax, ay, bx, by);
|
||
}
|
||
if (V) {
|
||
const double* V0 = V + 2 * (long long) k * ns;
|
||
const double* V1 = interp ? V0 + 2 * ns : V0;
|
||
double avx, avy, bvx, bvy;
|
||
if (interp) {
|
||
avx = V0[2 * mb] + w * (V1[2 * mb] - V0[2 * mb]);
|
||
avy = V0[2 * mb + 1] + w * (V1[2 * mb + 1] - V0[2 * mb + 1]);
|
||
bvx = V0[2 * mb + 2] + w * (V1[2 * mb + 2] - V0[2 * mb + 2]);
|
||
bvy = V0[2 * mb + 3] + w * (V1[2 * mb + 3] - V0[2 * mb + 3]);
|
||
} else {
|
||
avx = V0[2 * mb]; avy = V0[2 * mb + 1];
|
||
bvx = V0[2 * mb + 2]; bvy = V0[2 * mb + 3];
|
||
}
|
||
*vx = avx + ub * (bvx - avx);
|
||
*vy = avy + ub * (bvy - avy);
|
||
}
|
||
return best;
|
||
}
|
||
|
||
__device__ double geom_phi_at(double x, double y, double z, const GeomSdf& s, const double* __restrict__ poly)
|
||
{
|
||
/* the circle */
|
||
double ex0 = x - s.cx, ey0 = y - s.cy;
|
||
double dc = sqrt(ex0 * ex0 + ey0 * ey0) - s.rc;
|
||
if (s.cyl_cut) dc = span_cut(dc, z, s);
|
||
/* the capsule: distance to the polyline (or the plate, R8-c) */
|
||
double best = 1.0 / 0.0, axial = 0.0;
|
||
if (s.nst > 0) best = plate_dist(x, y, z, s, poly, nullptr, nullptr, nullptr, &axial);
|
||
else {
|
||
for (int m = 0; m + 1 < s.npts; ++m) {
|
||
double ax = poly[2 * m], ay = poly[2 * m + 1];
|
||
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
|
||
double ex = bx - ax, ey = by - ay;
|
||
double l2 = ex * ex + ey * ey;
|
||
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
|
||
if (u < 0.0) u = 0.0;
|
||
if (u > 1.0 && !(s.flat_tip && m + 2 == s.npts)) u = 1.0;
|
||
double px = ax + u * ex, py = ay + u * ey;
|
||
double qx = x - px, qy = y - py;
|
||
double d = sqrt(qx * qx + qy * qy);
|
||
if (d < best) best = d;
|
||
}
|
||
if (s.flat_tip) {
|
||
int m = s.npts - 2;
|
||
axial = tip_axial(x, y, poly[2 * m], poly[2 * m + 1], poly[2 * m + 2], poly[2 * m + 3]);
|
||
}
|
||
}
|
||
double df = best - s.half;
|
||
if (s.flat_tip) df = flat_cap(df, axial, s.tip_corner);
|
||
if (s.flag_cut) df = span_cut(df, z, s);
|
||
double r = s.fillet;
|
||
if (r > 0.0 && dc < r && df < r) {
|
||
double a = r - dc, b = r - df;
|
||
return r - sqrt(a * a + b * b);
|
||
}
|
||
return rs_min(dc, df);
|
||
}
|
||
|
||
/* Per corner. `has_prev`: keep φ where the decayed bound stays beyond the band. */
|
||
extern "C" __global__ void e3_geom_phi(
|
||
GeomGrid g, GeomSdf s, const double* __restrict__ poly,
|
||
int has_prev, double band, double motion,
|
||
double* __restrict__ phi, double* __restrict__ bound, unsigned char* __restrict__ touched)
|
||
{
|
||
long long n = (long long) blockIdx.x * blockDim.x + threadIdx.x;
|
||
long long nn = (long long) (g.nx + 1) * (g.ny + 1) * (g.nz + 1);
|
||
if (n >= nn) return;
|
||
long long nxy = (long long) (g.nx + 1) * (g.ny + 1);
|
||
int k = (int) (n / nxy);
|
||
int j = (int) ((n / (g.nx + 1)) % (g.ny + 1));
|
||
int i = (int) (n % (g.nx + 1));
|
||
if (has_prev) {
|
||
double b = bound[n] - motion;
|
||
if (b > band) {
|
||
bound[n] = b;
|
||
touched[n] = 0;
|
||
return;
|
||
}
|
||
}
|
||
double v = geom_phi_at((double) i * g.dx, (double) j * g.dy, (double) k * g.dz, s, poly);
|
||
phi[n] = v;
|
||
bound[n] = fabs(v);
|
||
touched[n] = 1;
|
||
}
|
||
|
||
__device__ __forceinline__ long long gnode(const GeomGrid& g, int k, int j, int i)
|
||
{
|
||
return ((long long) k * (g.ny + 1) + j) * (g.nx + 1) + i;
|
||
}
|
||
|
||
/* cut.rs tri_area_fraction */
|
||
__device__ double tri_frac(double p0, double p1, double p2)
|
||
{
|
||
double v[3] = {p0, p1, p2};
|
||
int pos = (p0 >= 0.0) + (p1 >= 0.0) + (p2 >= 0.0);
|
||
if (pos == 0) return 0.0;
|
||
if (pos == 3) return 1.0;
|
||
if (pos == 1) {
|
||
int a = v[0] >= 0.0 ? 0 : (v[1] >= 0.0 ? 1 : 2);
|
||
int b = (a + 1) % 3, c = (a + 2) % 3;
|
||
return (v[a] / (v[a] - v[b])) * (v[a] / (v[a] - v[c]));
|
||
}
|
||
int a = v[0] < 0.0 ? 0 : (v[1] < 0.0 ? 1 : 2);
|
||
int b = (a + 1) % 3, c = (a + 2) % 3;
|
||
return 1.0 - (v[a] / (v[a] - v[b])) * (v[a] / (v[a] - v[c]));
|
||
}
|
||
|
||
__device__ __forceinline__ double quad_frac(double q00, double q10, double q01, double q11)
|
||
{
|
||
return 0.5 * (tri_frac(q00, q10, q11) + tri_frac(q00, q11, q01));
|
||
}
|
||
|
||
/* Per face of component c (0 u, 1 v, 2 w): aperture and face-centre φ. */
|
||
extern "C" __global__ void e3_geom_faces(
|
||
GeomGrid g, int c, int full, const double* __restrict__ phi, const unsigned char* __restrict__ touched,
|
||
double* __restrict__ a, double* __restrict__ d)
|
||
{
|
||
long long f = (long long) blockIdx.x * blockDim.x + threadIdx.x;
|
||
int ni = g.nx + (c == 0), nj = g.ny + (c == 1), nk = g.nz + (c == 2);
|
||
if (f >= (long long) ni * nj * nk) return;
|
||
int k = (int) (f / ((long long) nj * ni));
|
||
int j = (int) ((f / ni) % nj);
|
||
int i = (int) (f % ni);
|
||
long long n00, n10, n01, n11;
|
||
if (c == 0) { /* x-face: (j, k) */
|
||
n00 = gnode(g, k, j, i); n10 = gnode(g, k, j + 1, i);
|
||
n01 = gnode(g, k + 1, j, i); n11 = gnode(g, k + 1, j + 1, i);
|
||
} else if (c == 1) { /* y-face: (i, k) */
|
||
n00 = gnode(g, k, j, i); n10 = gnode(g, k, j, i + 1);
|
||
n01 = gnode(g, k + 1, j, i); n11 = gnode(g, k + 1, j, i + 1);
|
||
} else { /* z-face: (i, j) */
|
||
n00 = gnode(g, k, j, i); n10 = gnode(g, k, j, i + 1);
|
||
n01 = gnode(g, k, j + 1, i); n11 = gnode(g, k, j + 1, i + 1);
|
||
}
|
||
if (!full && !(touched[n00] | touched[n10] | touched[n01] | touched[n11])) return;
|
||
double q00 = phi[n00], q10 = phi[n10], q01 = phi[n01], q11 = phi[n11];
|
||
a[f] = quad_frac(q00, q10, q01, q11);
|
||
d[f] = 0.25 * (q00 + q10 + q01 + q11);
|
||
}
|
||
|
||
__device__ __forceinline__ double det3(const double* a, const double* b, const double* c)
|
||
{
|
||
return a[0] * (b[1] * c[2] - b[2] * c[1]) - a[1] * (b[0] * c[2] - b[2] * c[0])
|
||
+ a[2] * (b[0] * c[1] - b[1] * c[0]);
|
||
}
|
||
|
||
__device__ double tet_vol(const double* p0, const double* p1, const double* p2, const double* p3)
|
||
{
|
||
double e1[3] = {p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]};
|
||
double e2[3] = {p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]};
|
||
double e3[3] = {p3[0] - p0[0], p3[1] - p0[1], p3[2] - p0[2]};
|
||
return fabs(det3(e1, e2, e3)) / 6.0;
|
||
}
|
||
|
||
__device__ __forceinline__ void lerp3(const double* a, const double* b, double t, double* out)
|
||
{
|
||
out[0] = a[0] + t * (b[0] - a[0]);
|
||
out[1] = a[1] + t * (b[1] - a[1]);
|
||
out[2] = a[2] + t * (b[2] - a[2]);
|
||
}
|
||
|
||
/* cut.rs tet_fluid_volume */
|
||
__device__ double tet_fluid(const double pts[4][3], const double* v)
|
||
{
|
||
double total = tet_vol(pts[0], pts[1], pts[2], pts[3]);
|
||
int pos[4], neg[4], np = 0, nn = 0;
|
||
for (int q = 0; q < 4; ++q) {
|
||
if (v[q] >= 0.0) pos[np++] = q;
|
||
}
|
||
for (int q = 0; q < 4; ++q) {
|
||
if (v[q] < 0.0) neg[nn++] = q;
|
||
}
|
||
if (np == 0) return 0.0;
|
||
if (np == 4) return total;
|
||
if (np == 1 || np == 3) {
|
||
int a = np == 1 ? pos[0] : neg[0];
|
||
const int* o = np == 1 ? neg : pos;
|
||
double pb[3], pc[3], pd[3];
|
||
lerp3(pts[a], pts[o[0]], v[a] / (v[a] - v[o[0]]), pb);
|
||
lerp3(pts[a], pts[o[1]], v[a] / (v[a] - v[o[1]]), pc);
|
||
lerp3(pts[a], pts[o[2]], v[a] / (v[a] - v[o[2]]), pd);
|
||
double t = tet_vol(pts[a], pb, pc, pd);
|
||
return np == 1 ? t : total - t;
|
||
}
|
||
int a = pos[0], b = pos[1], c = neg[0], d = neg[1];
|
||
double pac[3], pad[3], pbc[3], pbd[3];
|
||
lerp3(pts[a], pts[c], v[a] / (v[a] - v[c]), pac);
|
||
lerp3(pts[a], pts[d], v[a] / (v[a] - v[d]), pad);
|
||
lerp3(pts[b], pts[c], v[b] / (v[b] - v[c]), pbc);
|
||
lerp3(pts[b], pts[d], v[b] / (v[b] - v[d]), pbd);
|
||
return tet_vol(pts[a], pts[b], pbc, pbd) + tet_vol(pts[a], pac, pbc, pbd)
|
||
+ tet_vol(pts[a], pac, pad, pbd);
|
||
}
|
||
|
||
/* The Kuhn split around (0,0,0)–(1,1,1), corners as (x, y, z). */
|
||
__constant__ int KUHN[6][4][3] = {
|
||
{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {1, 1, 1}},
|
||
{{0, 0, 0}, {1, 0, 0}, {1, 0, 1}, {1, 1, 1}},
|
||
{{0, 0, 0}, {0, 1, 0}, {1, 1, 0}, {1, 1, 1}},
|
||
{{0, 0, 0}, {0, 1, 0}, {0, 1, 1}, {1, 1, 1}},
|
||
{{0, 0, 0}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1}},
|
||
{{0, 0, 0}, {0, 0, 1}, {0, 1, 1}, {1, 1, 1}},
|
||
};
|
||
|
||
/* Per cell: fluid volume fraction and the wall vector (3 interleaved). */
|
||
extern "C" __global__ void e3_geom_cells(
|
||
GeomGrid g, int full, const double* __restrict__ phi, const unsigned char* __restrict__ touched,
|
||
const double* __restrict__ a_u, const double* __restrict__ a_v, const double* __restrict__ a_w,
|
||
double* __restrict__ vol, double* __restrict__ wall, unsigned char* __restrict__ touched_cell)
|
||
{
|
||
long long idx = (long long) blockIdx.x * blockDim.x + threadIdx.x;
|
||
long long nxy = (long long) g.nx * g.ny;
|
||
if (idx >= nxy * g.nz) return;
|
||
int k = (int) (idx / nxy);
|
||
int j = (int) ((idx / g.nx) % g.ny);
|
||
int i = (int) (idx % g.nx);
|
||
unsigned char t = 0;
|
||
for (int dk = 0; dk < 2; ++dk)
|
||
for (int dj = 0; dj < 2; ++dj)
|
||
for (int di = 0; di < 2; ++di)
|
||
t |= touched[gnode(g, k + dk, j + dj, i + di)];
|
||
touched_cell[idx] = t;
|
||
if (!full && !t) return;
|
||
double fluid = 0.0;
|
||
for (int q = 0; q < 6; ++q) {
|
||
double pts[4][3];
|
||
double vals[4];
|
||
for (int m = 0; m < 4; ++m) {
|
||
pts[m][0] = (double) KUHN[q][m][0];
|
||
pts[m][1] = (double) KUHN[q][m][1];
|
||
pts[m][2] = (double) KUHN[q][m][2];
|
||
vals[m] = phi[gnode(g, k + KUHN[q][m][2], j + KUHN[q][m][1], i + KUHN[q][m][0])];
|
||
}
|
||
fluid += tet_fluid(pts, vals);
|
||
}
|
||
vol[idx] = fluid;
|
||
double ax = g.dy * g.dz, ay = g.dx * g.dz, az = g.dx * g.dy;
|
||
long long fu0 = ((long long) k * g.ny + j) * (g.nx + 1) + i;
|
||
long long fv0 = ((long long) k * (g.ny + 1) + j) * g.nx + i;
|
||
long long fw0 = ((long long) k * g.ny + j) * g.nx + i;
|
||
double sx = (a_u[fu0 + 1] - a_u[fu0]) * ax;
|
||
double sy = (a_v[fv0 + g.nx] - a_v[fv0]) * ay;
|
||
double sz = (a_w[fw0 + nxy] - a_w[fw0]) * az;
|
||
wall[3 * idx] = -sx;
|
||
wall[3 * idx + 1] = -sy;
|
||
wall[3 * idx + 2] = -sz;
|
||
}
|
||
|
||
/* out[w t + c] = src[w idx[t] + c], c < w (compact copies for the host mirror). */
|
||
extern "C" __global__ void e3_geom_gather(
|
||
int n, int w, const unsigned int* __restrict__ idx, const double* __restrict__ src, double* __restrict__ out)
|
||
{
|
||
int t = blockIdx.x * blockDim.x + threadIdx.x;
|
||
if (t >= n) return;
|
||
long long s = (long long) w * idx[t];
|
||
for (int c = 0; c < w; ++c) out[(long long) w * t + c] = src[s + c];
|
||
}
|
||
|
||
/*
|
||
* R6-2 step 2: the body's surface velocity on the device (the flag test's
|
||
* host closure: the centreline's velocity interpolated at the capsule's
|
||
* closest point where the span-cut capsule is not farther than the circle,
|
||
* zero on the circle, no z component), and its two uses on a moving cut
|
||
* mask:
|
||
* e3_geom_ub the imposition band's faces: the velocity at the foot
|
||
* (`Mask::surface_velocity_at`: the face centre plus its
|
||
* centroid shift when the centroid foot is on, φ and its
|
||
* unit gradient from the trilinear interpolant of the
|
||
* corner φ, the foot x − s n) into the dense table and a
|
||
* packed copy for the host mirror;
|
||
* e3_geom_impose the band's solid faces (not open at the instant, off the
|
||
* domain sides the host loops skip): the velocity at the
|
||
* face centre into the field (`Mask::impose_from`, whose
|
||
* ghost lists are empty on a cut mask);
|
||
* e3_geom_seam the periodic seam: w at k = nz takes k = 0's where both
|
||
* are solid.
|
||
*/
|
||
__device__ double body_velocity(double x, double y, double z, int c, const GeomSdf& s,
|
||
const double* __restrict__ poly, const double* __restrict__ vel)
|
||
{
|
||
double best = 1.0 / 0.0, vx = 0.0, vy = 0.0, axial = 0.0;
|
||
if (s.nst > 0) best = plate_dist(x, y, z, s, poly, vel, &vx, &vy, &axial);
|
||
else {
|
||
for (int m = 0; m + 1 < s.npts; ++m) {
|
||
double ax = poly[2 * m], ay = poly[2 * m + 1];
|
||
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
|
||
double ex = bx - ax, ey = by - ay;
|
||
double l2 = ex * ex + ey * ey;
|
||
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
|
||
if (u < 0.0) u = 0.0;
|
||
if (u > 1.0 && !(s.flat_tip && m + 2 == s.npts)) u = 1.0;
|
||
double px = ax + u * ex, py = ay + u * ey;
|
||
double qx = x - px, qy = y - py;
|
||
double d = sqrt(qx * qx + qy * qy);
|
||
if (d < best) {
|
||
best = d;
|
||
double uv = u > 1.0 ? 1.0 : u;
|
||
double avx = vel[2 * m], avy = vel[2 * m + 1];
|
||
double bvx = vel[2 * m + 2], bvy = vel[2 * m + 3];
|
||
vx = avx + uv * (bvx - avx);
|
||
vy = avy + uv * (bvy - avy);
|
||
}
|
||
}
|
||
if (s.flat_tip) {
|
||
int m = s.npts - 2;
|
||
axial = tip_axial(x, y, poly[2 * m], poly[2 * m + 1], poly[2 * m + 2], poly[2 * m + 3]);
|
||
}
|
||
}
|
||
double df = best - s.half;
|
||
if (s.flat_tip) df = flat_cap(df, axial, s.tip_corner);
|
||
if (s.flag_cut) df = span_cut(df, z, s);
|
||
double ex0 = x - s.cx, ey0 = y - s.cy;
|
||
double dc = sqrt(ex0 * ex0 + ey0 * ey0) - s.rc;
|
||
if (s.cyl_cut) dc = span_cut(dc, z, s);
|
||
if (df <= dc) return c == 0 ? vx : (c == 1 ? vy : 0.0);
|
||
return 0.0;
|
||
}
|
||
|
||
__device__ __forceinline__ double ub_node(const GeomGrid& g, const double* __restrict__ phi,
|
||
long long i, long long j, long long k)
|
||
{
|
||
if (i < 0) i = 0;
|
||
if (i > g.nx) i = g.nx;
|
||
if (j < 0) j = 0;
|
||
if (j > g.ny) j = g.ny;
|
||
if (k < 0) k = 0;
|
||
if (k > g.nz) k = g.nz;
|
||
return phi[(k * (g.ny + 1) + j) * (g.nx + 1) + i];
|
||
}
|
||
|
||
__device__ __forceinline__ double ub_lerp(double a, double b, double f) { return a + f * (b - a); }
|
||
|
||
/* The face centre of face `f` of component `c`. */
|
||
__device__ __forceinline__ void face_centre(const GeomGrid& g, int c, long long f, double* x)
|
||
{
|
||
long long ni = g.nx + (c == 0), nj = g.ny + (c == 1);
|
||
long long k = f / (nj * ni), j = (f / ni) % nj, i = f % ni;
|
||
x[0] = ((double) i + (c == 0 ? 0.0 : 0.5)) * g.dx;
|
||
x[1] = ((double) j + (c == 1 ? 0.0 : 0.5)) * g.dy;
|
||
x[2] = ((double) k + (c == 2 ? 0.0 : 0.5)) * g.dz;
|
||
}
|
||
|
||
extern "C" __global__ void e3_geom_ub(
|
||
GeomGrid g, GeomSdf s, const double* __restrict__ poly, const double* __restrict__ vel,
|
||
const double* __restrict__ phi, int c, const unsigned int* __restrict__ faces, long long nf,
|
||
const double* __restrict__ shift, int use_shift,
|
||
double* __restrict__ ub, double* __restrict__ packed)
|
||
{
|
||
long long q = (long long) blockIdx.x * blockDim.x + threadIdx.x;
|
||
if (q >= nf) return;
|
||
long long f = faces[q];
|
||
double xc[3];
|
||
face_centre(g, c, f, xc);
|
||
double x0 = xc[0], x1 = xc[1], x2 = xc[2];
|
||
if (use_shift) {
|
||
x0 = x0 + shift[3 * f];
|
||
x1 = x1 + shift[3 * f + 1];
|
||
x2 = x2 + shift[3 * f + 2];
|
||
}
|
||
/* the interpolant's value and unit gradient */
|
||
double gx = x0 / g.dx, gy = x1 / g.dy, gz = x2 / g.dz;
|
||
long long i0 = (long long) floor(gx), j0 = (long long) floor(gy), k0 = (long long) floor(gz);
|
||
double fx = gx - (double) i0, fy = gy - (double) j0, fz = gz - (double) k0;
|
||
double c000 = ub_node(g, phi, i0, j0, k0), c100 = ub_node(g, phi, i0 + 1, j0, k0);
|
||
double c010 = ub_node(g, phi, i0, j0 + 1, k0), c110 = ub_node(g, phi, i0 + 1, j0 + 1, k0);
|
||
double c001 = ub_node(g, phi, i0, j0, k0 + 1), c101 = ub_node(g, phi, i0 + 1, j0, k0 + 1);
|
||
double c011 = ub_node(g, phi, i0, j0 + 1, k0 + 1), c111 = ub_node(g, phi, i0 + 1, j0 + 1, k0 + 1);
|
||
double c00 = ub_lerp(c000, c100, fx);
|
||
double c10 = ub_lerp(c010, c110, fx);
|
||
double c01 = ub_lerp(c001, c101, fx);
|
||
double c11 = ub_lerp(c011, c111, fx);
|
||
double c0 = ub_lerp(c00, c10, fy);
|
||
double c1 = ub_lerp(c01, c11, fy);
|
||
double sd = ub_lerp(c0, c1, fz);
|
||
double dx0 = ub_lerp(c100 - c000, c110 - c010, fy);
|
||
double dx1 = ub_lerp(c101 - c001, c111 - c011, fy);
|
||
double px = ub_lerp(dx0, dx1, fz) / g.dx;
|
||
double dy0 = ub_lerp(c010 - c000, c110 - c100, fx);
|
||
double dy1 = ub_lerp(c011 - c001, c111 - c101, fx);
|
||
double py = ub_lerp(dy0, dy1, fz) / g.dy;
|
||
double dz0 = ub_lerp(c001 - c000, c101 - c100, fx);
|
||
double dz1 = ub_lerp(c011 - c010, c111 - c110, fx);
|
||
double pz = ub_lerp(dz0, dz1, fy) / g.dz;
|
||
double norm = sqrt(px * px + py * py + pz * pz);
|
||
double n0 = 1.0, n1 = 0.0, n2 = 0.0;
|
||
if (norm > 0.0) {
|
||
n0 = px / norm;
|
||
n1 = py / norm;
|
||
n2 = pz / norm;
|
||
}
|
||
double v = body_velocity(x0 - sd * n0, x1 - sd * n1, x2 - sd * n2, c, s, poly, vel);
|
||
ub[f] = v;
|
||
packed[q] = v;
|
||
}
|
||
|
||
extern "C" __global__ void e3_geom_impose(
|
||
GeomGrid g, GeomSdf s, const double* __restrict__ poly, const double* __restrict__ vel,
|
||
int c, const unsigned int* __restrict__ faces, long long nf,
|
||
const int* __restrict__ open, double* __restrict__ field)
|
||
{
|
||
long long q = (long long) blockIdx.x * blockDim.x + threadIdx.x;
|
||
if (q >= nf) return;
|
||
long long f = faces[q];
|
||
if (open[f] != 0) return;
|
||
long long ni = g.nx + (c == 0), nj = g.ny + (c == 1);
|
||
long long j = (f / ni) % nj, i = f % ni;
|
||
/* the host loops: u over i in 1..nx, v over j in 1..ny, every w */
|
||
if (c == 0 && (i == 0 || i == g.nx)) return;
|
||
if (c == 1 && (j == 0 || j == g.ny)) return;
|
||
double xc[3];
|
||
face_centre(g, c, f, xc);
|
||
field[f] = body_velocity(xc[0], xc[1], xc[2], c, s, poly, vel);
|
||
}
|
||
|
||
extern "C" __global__ void e3_geom_seam(GeomGrid g, const int* __restrict__ open_w, double* __restrict__ w)
|
||
{
|
||
long long n = (long long) blockIdx.x * blockDim.x + threadIdx.x;
|
||
long long nxy = (long long) g.nx * g.ny;
|
||
if (n >= nxy) return;
|
||
long long fn = (long long) g.nz * nxy + n;
|
||
if (open_w[n] == 0 && open_w[fn] == 0) w[fn] = w[n];
|
||
}
|