Files
rustytorch/crates/specialized/rtx-cfd/src/kernels/cuda/e3_geom.cu
T
Omar SobhandClaude Opus 5.5 02ab155022 R8-h: a flat-tipped flag for the 3D solver (default off, byte-identical when off)
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]>
2026-09-25 22:32:31 -05:00

577 lines
24 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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];
}