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]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
171da41ed1
commit
02ab155022
@@ -25,6 +25,8 @@ struct GeomSdf {
|
|||||||
int cyl_cut, flag_cut; /* cut to the span */
|
int cyl_cut, flag_cut; /* cut to the span */
|
||||||
int npts; /* polyline points (x, y interleaved) */
|
int npts; /* polyline points (x, y interleaved) */
|
||||||
int nst, ns; /* R8-c plate: stations and points per station (nst 0: the polyline) */
|
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 {
|
struct GeomGrid {
|
||||||
@@ -46,6 +48,24 @@ __device__ __forceinline__ double span_cut(double d2, double z, const GeomSdf& s
|
|||||||
return outside + rs_min(rs_max(q1, q2), 0.0) - r;
|
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
|
* R8-c: the deformed plate (`plate.rs`, the host twin expression for
|
||||||
* expression): `poly` holds the stations' rows (x, y interleaved, row-major
|
* expression): `poly` holds the stations' rows (x, y interleaved, row-major
|
||||||
@@ -57,7 +77,7 @@ __device__ __forceinline__ double span_cut(double d2, double z, const GeomSdf& s
|
|||||||
*/
|
*/
|
||||||
__device__ double plate_dist(double x, double y, double z, const GeomSdf& s,
|
__device__ double plate_dist(double x, double y, double z, const GeomSdf& s,
|
||||||
const double* __restrict__ P, const double* __restrict__ V,
|
const double* __restrict__ P, const double* __restrict__ V,
|
||||||
double* vx, double* vy)
|
double* vx, double* vy, double* axial)
|
||||||
{
|
{
|
||||||
int nst = s.nst, ns = s.ns;
|
int nst = s.nst, ns = s.ns;
|
||||||
const double* Z = P + 2 * (long long) nst * ns;
|
const double* Z = P + 2 * (long long) nst * ns;
|
||||||
@@ -97,14 +117,14 @@ __device__ double plate_dist(double x, double y, double z, const GeomSdf& s,
|
|||||||
double l2 = ex * ex + ey * ey;
|
double l2 = ex * ex + ey * ey;
|
||||||
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
|
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
|
||||||
if (u < 0.0) u = 0.0;
|
if (u < 0.0) u = 0.0;
|
||||||
if (u > 1.0) u = 1.0;
|
if (u > 1.0 && !(s.flat_tip && m + 2 == ns)) u = 1.0;
|
||||||
double px = ax + u * ex, py = ay + u * ey;
|
double px = ax + u * ex, py = ay + u * ey;
|
||||||
double qx = x - px, qy = y - py;
|
double qx = x - px, qy = y - py;
|
||||||
double d = sqrt(qx * qx + qy * qy);
|
double d = sqrt(qx * qx + qy * qy);
|
||||||
if (d < best) {
|
if (d < best) {
|
||||||
best = d;
|
best = d;
|
||||||
mb = m;
|
mb = m;
|
||||||
ub = u;
|
ub = u > 1.0 ? 1.0 : u;
|
||||||
qbx = qx;
|
qbx = qx;
|
||||||
qby = qy;
|
qby = qy;
|
||||||
}
|
}
|
||||||
@@ -118,6 +138,20 @@ __device__ double plate_dist(double x, double y, double z, const GeomSdf& s,
|
|||||||
double qn = (qbx * cx + qby * cy) / best;
|
double qn = (qbx * cx + qby * cy) / best;
|
||||||
best = best / sqrt(1.0 + qn * qn);
|
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) {
|
if (V) {
|
||||||
const double* V0 = V + 2 * (long long) k * ns;
|
const double* V0 = V + 2 * (long long) k * ns;
|
||||||
const double* V1 = interp ? V0 + 2 * ns : V0;
|
const double* V1 = interp ? V0 + 2 * ns : V0;
|
||||||
@@ -144,22 +178,29 @@ __device__ double geom_phi_at(double x, double y, double z, const GeomSdf& s, co
|
|||||||
double dc = sqrt(ex0 * ex0 + ey0 * ey0) - s.rc;
|
double dc = sqrt(ex0 * ex0 + ey0 * ey0) - s.rc;
|
||||||
if (s.cyl_cut) dc = span_cut(dc, z, s);
|
if (s.cyl_cut) dc = span_cut(dc, z, s);
|
||||||
/* the capsule: distance to the polyline (or the plate, R8-c) */
|
/* the capsule: distance to the polyline (or the plate, R8-c) */
|
||||||
double best = 1.0 / 0.0;
|
double best = 1.0 / 0.0, axial = 0.0;
|
||||||
if (s.nst > 0) best = plate_dist(x, y, z, s, poly, nullptr, nullptr, nullptr);
|
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) {
|
else {
|
||||||
|
for (int m = 0; m + 1 < s.npts; ++m) {
|
||||||
double ax = poly[2 * m], ay = poly[2 * m + 1];
|
double ax = poly[2 * m], ay = poly[2 * m + 1];
|
||||||
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
|
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
|
||||||
double ex = bx - ax, ey = by - ay;
|
double ex = bx - ax, ey = by - ay;
|
||||||
double l2 = ex * ex + ey * ey;
|
double l2 = ex * ex + ey * ey;
|
||||||
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
|
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
|
||||||
if (u < 0.0) u = 0.0;
|
if (u < 0.0) u = 0.0;
|
||||||
if (u > 1.0) u = 1.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 px = ax + u * ex, py = ay + u * ey;
|
||||||
double qx = x - px, qy = y - py;
|
double qx = x - px, qy = y - py;
|
||||||
double d = sqrt(qx * qx + qy * qy);
|
double d = sqrt(qx * qx + qy * qy);
|
||||||
if (d < best) best = d;
|
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;
|
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);
|
if (s.flag_cut) df = span_cut(df, z, s);
|
||||||
double r = s.fillet;
|
double r = s.fillet;
|
||||||
if (r > 0.0 && dc < r && df < r) {
|
if (r > 0.0 && dc < r && df < r) {
|
||||||
@@ -391,28 +432,36 @@ extern "C" __global__ void e3_geom_gather(
|
|||||||
__device__ double body_velocity(double x, double y, double z, int c, const GeomSdf& s,
|
__device__ double body_velocity(double x, double y, double z, int c, const GeomSdf& s,
|
||||||
const double* __restrict__ poly, const double* __restrict__ vel)
|
const double* __restrict__ poly, const double* __restrict__ vel)
|
||||||
{
|
{
|
||||||
double best = 1.0 / 0.0, vx = 0.0, vy = 0.0;
|
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);
|
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) {
|
else {
|
||||||
|
for (int m = 0; m + 1 < s.npts; ++m) {
|
||||||
double ax = poly[2 * m], ay = poly[2 * m + 1];
|
double ax = poly[2 * m], ay = poly[2 * m + 1];
|
||||||
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
|
double bx = poly[2 * m + 2], by = poly[2 * m + 3];
|
||||||
double ex = bx - ax, ey = by - ay;
|
double ex = bx - ax, ey = by - ay;
|
||||||
double l2 = ex * ex + ey * ey;
|
double l2 = ex * ex + ey * ey;
|
||||||
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
|
double u = ((x - ax) * ex + (y - ay) * ey) / l2;
|
||||||
if (u < 0.0) u = 0.0;
|
if (u < 0.0) u = 0.0;
|
||||||
if (u > 1.0) u = 1.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 px = ax + u * ex, py = ay + u * ey;
|
||||||
double qx = x - px, qy = y - py;
|
double qx = x - px, qy = y - py;
|
||||||
double d = sqrt(qx * qx + qy * qy);
|
double d = sqrt(qx * qx + qy * qy);
|
||||||
if (d < best) {
|
if (d < best) {
|
||||||
best = d;
|
best = d;
|
||||||
|
double uv = u > 1.0 ? 1.0 : u;
|
||||||
double avx = vel[2 * m], avy = vel[2 * m + 1];
|
double avx = vel[2 * m], avy = vel[2 * m + 1];
|
||||||
double bvx = vel[2 * m + 2], bvy = vel[2 * m + 3];
|
double bvx = vel[2 * m + 2], bvy = vel[2 * m + 3];
|
||||||
vx = avx + u * (bvx - avx);
|
vx = avx + uv * (bvx - avx);
|
||||||
vy = avy + u * (bvy - avy);
|
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;
|
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);
|
if (s.flag_cut) df = span_cut(df, z, s);
|
||||||
double ex0 = x - s.cx, ey0 = y - s.cy;
|
double ex0 = x - s.cx, ey0 = y - s.cy;
|
||||||
double dc = sqrt(ex0 * ex0 + ey0 * ey0) - s.rc;
|
double dc = sqrt(ex0 * ex0 + ey0 * ey0) - s.rc;
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ pub struct DeviceSdf {
|
|||||||
pub half: f64,
|
pub half: f64,
|
||||||
/// The root fillet radius (0 = the plain union).
|
/// The root fillet radius (0 = the plain union).
|
||||||
pub fillet: f64,
|
pub fillet: f64,
|
||||||
|
/// R8-h: the tip. `None` = the capsule (the semicircular tip around the
|
||||||
|
/// last point, as before); `Some(r_c)` = a FLAT tip: the flag ends in
|
||||||
|
/// the plane through the last point normal to the last segment, its two
|
||||||
|
/// corners rounded to radius `r_c` (0 ≤ r_c ≤ `half`; `r_c = half` is
|
||||||
|
/// the capsule's semicircle pulled back by `half`, geometrically).
|
||||||
|
pub tip_corner: Option<f64>,
|
||||||
/// The centreline polyline's points (x, y) at `t`.
|
/// The centreline polyline's points (x, y) at `t`.
|
||||||
pub poly: Vec<[f64; 2]>,
|
pub poly: Vec<[f64; 2]>,
|
||||||
/// R6-2 step 2: the centreline's velocity (vx, vy) per point at `t`, the
|
/// R6-2 step 2: the centreline's velocity (vx, vy) per point at `t`, the
|
||||||
|
|||||||
@@ -708,6 +708,7 @@ mod tests {
|
|||||||
r_edge: 0.0,
|
r_edge: 0.0,
|
||||||
half,
|
half,
|
||||||
fillet: 0.0,
|
fillet: 0.0,
|
||||||
|
tip_corner: None,
|
||||||
poly: Vec::new(),
|
poly: Vec::new(),
|
||||||
vel: Vec::new(),
|
vel: Vec::new(),
|
||||||
plate: Some(surf),
|
plate: Some(surf),
|
||||||
|
|||||||
@@ -131,12 +131,16 @@ fn span_cut(d2: f64, z: f64, s: &DeviceSdf) -> f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The closest segment of a polyline given by `pt(m)`, `m < n`: the
|
/// The closest segment of a polyline given by `pt(m)`, `m < n`: the
|
||||||
/// in-plane distance, the segment, its parameter and the offset `(qx, qy)`.
|
/// in-plane distance, the segment, its parameter (clamped to the segment)
|
||||||
|
/// and the offset `(qx, qy)`. R8-h: with `ray_last` the last segment is a
|
||||||
|
/// RAY beyond its end point (its parameter unclamped above for the
|
||||||
|
/// distance) — the lateral distance the flat tip's cap needs.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn closest<P: Fn(usize) -> [f64; 2]>(
|
fn closest<P: Fn(usize) -> [f64; 2]>(
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
n: usize,
|
n: usize,
|
||||||
|
ray_last: bool,
|
||||||
pt: P,
|
pt: P,
|
||||||
) -> (f64, usize, f64, [f64; 2]) {
|
) -> (f64, usize, f64, [f64; 2]) {
|
||||||
let mut best = f64::INFINITY;
|
let mut best = f64::INFINITY;
|
||||||
@@ -151,7 +155,7 @@ fn closest<P: Fn(usize) -> [f64; 2]>(
|
|||||||
if u < 0.0 {
|
if u < 0.0 {
|
||||||
u = 0.0;
|
u = 0.0;
|
||||||
}
|
}
|
||||||
if u > 1.0 {
|
if u > 1.0 && !(ray_last && m + 2 == n) {
|
||||||
u = 1.0;
|
u = 1.0;
|
||||||
}
|
}
|
||||||
let px = ax + u * ex;
|
let px = ax + u * ex;
|
||||||
@@ -162,25 +166,63 @@ fn closest<P: Fn(usize) -> [f64; 2]>(
|
|||||||
if d < best {
|
if d < best {
|
||||||
best = d;
|
best = d;
|
||||||
mb = m;
|
mb = m;
|
||||||
ub = u;
|
ub = if u > 1.0 { 1.0 } else { u };
|
||||||
qb = [qx, qy];
|
qb = [qx, qy];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(best, mb, ub, qb)
|
(best, mb, ub, qb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// R8-h: the signed distance along the last segment's unit tangent from
|
||||||
|
/// its end point `b` (the flat tip's plane; > 0 beyond the tip).
|
||||||
|
#[inline]
|
||||||
|
fn tip_axial(x: f64, y: f64, a: [f64; 2], b: [f64; 2]) -> f64 {
|
||||||
|
let ex = b[0] - a[0];
|
||||||
|
let ey = b[1] - a[1];
|
||||||
|
let l = (ex * ex + ey * ey).sqrt();
|
||||||
|
((x - b[0]) * ex + (y - b[1]) * ey) / l
|
||||||
|
}
|
||||||
|
|
||||||
|
/// R8-h: the flat tip — the strip of lateral distance `dl` (distance to
|
||||||
|
/// the centreline with its last segment a ray, minus the half-thickness)
|
||||||
|
/// cut by the tip's plane (axial distance `a`) with the two corners rounded
|
||||||
|
/// to radius `r` (the span cut's rounded intersection). Exact near the tip
|
||||||
|
/// where the centreline is straight over the corner (the last segment).
|
||||||
|
#[inline]
|
||||||
|
fn flat_cap(dl: f64, a: f64, r: f64) -> f64 {
|
||||||
|
let q1 = dl + r;
|
||||||
|
let q2 = a + r;
|
||||||
|
let m1 = rs_max(q1, 0.0);
|
||||||
|
let m2 = rs_max(q2, 0.0);
|
||||||
|
let outside = (m1 * m1 + m2 * m2).sqrt();
|
||||||
|
outside + rs_min(rs_max(q1, q2), 0.0) - r
|
||||||
|
}
|
||||||
|
|
||||||
/// The plate's in-plane closest point at `(x, y, z)`, the slope-corrected
|
/// The plate's in-plane closest point at `(x, y, z)`, the slope-corrected
|
||||||
/// distance to the mid-surface, and the interpolated velocity there
|
/// distance to the mid-surface, and the interpolated velocity there
|
||||||
/// (zero without velocities).
|
/// (zero without velocities).
|
||||||
fn plate_closest(p: &PlateSurface, x: f64, y: f64, z: f64) -> (f64, [f64; 2]) {
|
///
|
||||||
|
/// R8-h: with `flat` the last segment is a ray (see [`closest`]) and the
|
||||||
|
/// third value is the tip's axial distance on the interpolated polyline
|
||||||
|
/// (0 otherwise).
|
||||||
|
fn plate_closest(p: &PlateSurface, x: f64, y: f64, z: f64, flat: bool) -> (f64, [f64; 2], f64) {
|
||||||
let ns = p.ns;
|
let ns = p.ns;
|
||||||
let (k, w) = p.bracket(z);
|
let (k, w) = p.bracket(z);
|
||||||
let row = |k: usize, m: usize| p.xy[k * ns + m];
|
let row = |k: usize, m: usize| p.xy[k * ns + m];
|
||||||
let lerp2 =
|
let lerp2 =
|
||||||
|a: [f64; 2], b: [f64; 2], w: f64| [a[0] + w * (b[0] - a[0]), a[1] + w * (b[1] - a[1])];
|
|a: [f64; 2], b: [f64; 2], w: f64| [a[0] + w * (b[0] - a[0]), a[1] + w * (b[1] - a[1])];
|
||||||
let (best, m, u, q) = match w {
|
let (best, m, u, q) = match w {
|
||||||
None => closest(x, y, ns, |m| row(k, m)),
|
None => closest(x, y, ns, flat, |m| row(k, m)),
|
||||||
Some(w) => closest(x, y, ns, |m| lerp2(row(k, m), row(k + 1, m), w)),
|
Some(w) => closest(x, y, ns, flat, |m| lerp2(row(k, m), row(k + 1, m), w)),
|
||||||
|
};
|
||||||
|
let axial = if flat {
|
||||||
|
let at = |m: usize| match w {
|
||||||
|
None => row(k, m),
|
||||||
|
Some(w) => lerp2(row(k, m), row(k + 1, m), w),
|
||||||
|
};
|
||||||
|
tip_axial(x, y, at(ns - 2), at(ns - 1))
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
};
|
};
|
||||||
let mut d = best;
|
let mut d = best;
|
||||||
if let Some(_w) = w {
|
if let Some(_w) = w {
|
||||||
@@ -211,7 +253,7 @@ fn plate_closest(p: &PlateSurface, x: f64, y: f64, z: f64) -> (f64, [f64; 2]) {
|
|||||||
let (a, b) = (at(m), at(m + 1));
|
let (a, b) = (at(m), at(m + 1));
|
||||||
[a[0] + u * (b[0] - a[0]), a[1] + u * (b[1] - a[1])]
|
[a[0] + u * (b[0] - a[0]), a[1] + u * (b[1] - a[1])]
|
||||||
};
|
};
|
||||||
(d, v)
|
(d, v, axial)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceSdf {
|
impl DeviceSdf {
|
||||||
@@ -231,20 +273,30 @@ impl DeviceSdf {
|
|||||||
/// and its surface velocity at the closest point.
|
/// and its surface velocity at the closest point.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn flag_distance_host(&self, x: f64, y: f64, z: f64) -> (f64, [f64; 2]) {
|
pub fn flag_distance_host(&self, x: f64, y: f64, z: f64) -> (f64, [f64; 2]) {
|
||||||
let (best, v) = match self.plate.as_ref() {
|
let flat = self.tip_corner.is_some();
|
||||||
Some(p) => plate_closest(p, x, y, z),
|
let (best, v, axial) = match self.plate.as_ref() {
|
||||||
|
Some(p) => plate_closest(p, x, y, z, flat),
|
||||||
None => {
|
None => {
|
||||||
let (best, m, u, _) = closest(x, y, self.poly.len(), |m| self.poly[m]);
|
let n = self.poly.len();
|
||||||
|
let (best, m, u, _) = closest(x, y, n, flat, |m| self.poly[m]);
|
||||||
let v = if self.vel.len() == self.poly.len() && !self.vel.is_empty() {
|
let v = if self.vel.len() == self.poly.len() && !self.vel.is_empty() {
|
||||||
let (a, b) = (self.vel[m], self.vel[m + 1]);
|
let (a, b) = (self.vel[m], self.vel[m + 1]);
|
||||||
[a[0] + u * (b[0] - a[0]), a[1] + u * (b[1] - a[1])]
|
[a[0] + u * (b[0] - a[0]), a[1] + u * (b[1] - a[1])]
|
||||||
} else {
|
} else {
|
||||||
[0.0, 0.0]
|
[0.0, 0.0]
|
||||||
};
|
};
|
||||||
(best, v)
|
let axial = if flat {
|
||||||
|
tip_axial(x, y, self.poly[n - 2], self.poly[n - 1])
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
(best, v, axial)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut df = best - self.half;
|
let mut df = best - self.half;
|
||||||
|
if let Some(rc) = self.tip_corner {
|
||||||
|
df = flat_cap(df, axial, rc);
|
||||||
|
}
|
||||||
if self.flag_cut {
|
if self.flag_cut {
|
||||||
df = span_cut(df, z, self);
|
df = span_cut(df, z, self);
|
||||||
}
|
}
|
||||||
@@ -344,6 +396,7 @@ mod tests {
|
|||||||
r_edge: 0.0066,
|
r_edge: 0.0066,
|
||||||
half: 0.01,
|
half: 0.01,
|
||||||
fillet: 0.0,
|
fillet: 0.0,
|
||||||
|
tip_corner: None,
|
||||||
poly,
|
poly,
|
||||||
vel,
|
vel,
|
||||||
plate,
|
plate,
|
||||||
@@ -406,7 +459,7 @@ mod tests {
|
|||||||
vel: Vec::new(),
|
vel: Vec::new(),
|
||||||
};
|
};
|
||||||
for &(y, zz) in &[(0.53, 0.05), (0.47, 0.15), (0.6, 0.12)] {
|
for &(y, zz) in &[(0.53, 0.05), (0.47, 0.15), (0.6, 0.12)] {
|
||||||
let (d, _) = plate_closest(&p, 0.1, y, zz);
|
let (d, _, _) = plate_closest(&p, 0.1, y, zz, false);
|
||||||
let exact = (y - (0.5 + a * (zz - 0.1))).abs() / (1.0 + a * a).sqrt();
|
let exact = (y - (0.5 + a * (zz - 0.1))).abs() / (1.0 + a * a).sqrt();
|
||||||
assert!((d - exact).abs() < 1e-15, "{d} vs {exact}");
|
assert!((d - exact).abs() < 1e-15, "{d} vs {exact}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ struct GeomSdf {
|
|||||||
npts: i32,
|
npts: i32,
|
||||||
nst: i32,
|
nst: i32,
|
||||||
ns: i32,
|
ns: i32,
|
||||||
|
/// R8-h: the flat tip (1) and its corner radius.
|
||||||
|
flat_tip: i32,
|
||||||
|
tip_corner: f64,
|
||||||
}
|
}
|
||||||
unsafe impl DeviceRepr for GeomSdf {}
|
unsafe impl DeviceRepr for GeomSdf {}
|
||||||
unsafe impl ValidAsZeroBits for GeomSdf {}
|
unsafe impl ValidAsZeroBits for GeomSdf {}
|
||||||
@@ -151,6 +154,8 @@ fn geom_sdf(sdf: &DeviceSdf) -> GeomSdf {
|
|||||||
npts: sdf.poly.len() as i32,
|
npts: sdf.poly.len() as i32,
|
||||||
nst: sdf.plate.as_ref().map_or(0, |p| p.z.len() as i32),
|
nst: sdf.plate.as_ref().map_or(0, |p| p.z.len() as i32),
|
||||||
ns: sdf.plate.as_ref().map_or(0, |p| p.ns as i32),
|
ns: sdf.plate.as_ref().map_or(0, |p| p.ns as i32),
|
||||||
|
flat_tip: i32::from(sdf.tip_corner.is_some()),
|
||||||
|
tip_corner: sdf.tip_corner.unwrap_or(0.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,8 +96,35 @@ fn root_fillet() -> f64 {
|
|||||||
/// record before this date had the apex 10 mm beyond A (`=0` restores
|
/// record before this date had the apex 10 mm beyond A (`=0` restores
|
||||||
/// them): at ny 62 on the recorded motion that was drag 253.3 → 240.0 and
|
/// them): at ny 62 on the recorded motion that was drag 253.3 → 240.0 and
|
||||||
/// the lift swing 1,005 → 836 (the overset's 867).
|
/// the lift swing 1,005 → 836 (the overset's 867).
|
||||||
|
///
|
||||||
|
/// R8-h: with the flat tip the default inset is 0 (the flat face through A).
|
||||||
fn tip_inset() -> f64 {
|
fn tip_inset() -> f64 {
|
||||||
env_f("RTX_E3_FLAG_TIP_INSET", FLAG_HALF)
|
env_f(
|
||||||
|
"RTX_E3_FLAG_TIP_INSET",
|
||||||
|
if flat_tip().is_some() { 0.0 } else { FLAG_HALF },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// R8-h (2026-09-25): the tip's shape. `RTX_E3_FLAG_TIP=flat` gives the
|
||||||
|
/// flag a FLAT tip through the centreline's last point (A, the inset
|
||||||
|
/// defaulting to 0), its corners rounded to `RTX_E3_FLAG_TIP_CORNER`
|
||||||
|
/// metres (default 0.00125, the 2D overset's recommended line; at most
|
||||||
|
/// `FLAG_HALF`); unset or `capsule` = the capsule (the semicircular tip).
|
||||||
|
/// The flat tip's host φ and surface velocity are the device form's
|
||||||
|
/// (`DeviceSdf::phi_host`, the kernel's arithmetic).
|
||||||
|
fn flat_tip() -> Option<f64> {
|
||||||
|
match std::env::var("RTX_E3_FLAG_TIP").as_deref() {
|
||||||
|
Err(_) | Ok("capsule") => None,
|
||||||
|
Ok("flat") => {
|
||||||
|
let rc = env_f("RTX_E3_FLAG_TIP_CORNER", 0.00125);
|
||||||
|
assert!(
|
||||||
|
(0.0..=FLAG_HALF).contains(&rc),
|
||||||
|
"RTX_E3_FLAG_TIP_CORNER {rc} outside [0, {FLAG_HALF}]"
|
||||||
|
);
|
||||||
|
Some(rc)
|
||||||
|
}
|
||||||
|
Ok(v) => panic!("RTX_E3_FLAG_TIP={v}: flat or capsule"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Smooth union with a concave fillet of radius `r` (the plain `min` at r = 0).
|
/// Smooth union with a concave fillet of radius `r` (the plain `min` at r = 0).
|
||||||
@@ -491,6 +518,7 @@ fn flag_wake_on_the_device() {
|
|||||||
r_edge,
|
r_edge,
|
||||||
half: FLAG_HALF,
|
half: FLAG_HALF,
|
||||||
fillet: r_fillet,
|
fillet: r_fillet,
|
||||||
|
tip_corner: flat_tip(),
|
||||||
poly: match (&plate, recorded()) {
|
poly: match (&plate, recorded()) {
|
||||||
(Some(_), _) => Vec::new(),
|
(Some(_), _) => Vec::new(),
|
||||||
(None, Some(rec)) => recorded_polyline(rec, t)
|
(None, Some(rec)) => recorded_polyline(rec, t)
|
||||||
@@ -527,9 +555,9 @@ fn flag_wake_on_the_device() {
|
|||||||
c.1.clone().expect("sdf")
|
c.1.clone().expect("sdf")
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let body = if plate_body() {
|
let body = if plate_body() || flat_tip().is_some() {
|
||||||
// R8-c: the host φ and surface velocity ARE the device form's (the
|
// R8-c: the host φ and surface velocity ARE the device form's (the
|
||||||
// kernel's arithmetic on the host).
|
// kernel's arithmetic on the host); R8-h: the flat tip too.
|
||||||
Body::from_sdf(move |x, y, z, t| sdf_at(t).phi_host(x, y, z))
|
Body::from_sdf(move |x, y, z, t| sdf_at(t).phi_host(x, y, z))
|
||||||
.with_surface_velocity(move |x, y, z, t| sdf_at(t).velocity_host(x, y, z))
|
.with_surface_velocity(move |x, y, z, t| sdf_at(t).velocity_host(x, y, z))
|
||||||
} else {
|
} else {
|
||||||
@@ -587,6 +615,9 @@ fn flag_wake_on_the_device() {
|
|||||||
g.cells(),
|
g.cells(),
|
||||||
(t_end / dt).ceil() as usize
|
(t_end / dt).ceil() as usize
|
||||||
);
|
);
|
||||||
|
if let Some(rc) = flat_tip() {
|
||||||
|
println!(" R8-h: FLAT tip through the centreline's last point, corner radius {rc:.5} m");
|
||||||
|
}
|
||||||
unsafe { std::env::set_var("RTX_PROFILE", "1") };
|
unsafe { std::env::set_var("RTX_PROFILE", "1") };
|
||||||
let mut device = DeviceStep::new(solver, g);
|
let mut device = DeviceStep::new(solver, g);
|
||||||
device.upload(&field);
|
device.upload(&field);
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
//! R8-h (2026-09-25): the flat-tipped flag's geometry (`DeviceSdf::tip_corner`,
|
||||||
|
//! host twin `DeviceSdf::phi_host` = the kernel's `geom_phi_at`).
|
||||||
|
//!
|
||||||
|
//! G2: a straight flag (a five-point polyline, root a semicircle, tip flat
|
||||||
|
//! with corners rounded to `r_c`) at two orientations, cut on the cubic grid
|
||||||
|
//! at three resolutions: the solid volume and the wall area of the cut
|
||||||
|
//! geometry against the analytic rounded rectangle; the tip plane through
|
||||||
|
//! the last point; `r_c = half` = the capsule pulled back by `half`.
|
||||||
|
|
||||||
|
use rtx_cfd::solvers::incompressible::embedded3::{Body, CutGeometry, DeviceSdf, Grid};
|
||||||
|
|
||||||
|
const HALF: f64 = 0.01;
|
||||||
|
const LEN: f64 = 0.35;
|
||||||
|
const P0: [f64; 2] = [0.05, 0.08];
|
||||||
|
|
||||||
|
fn flag(theta: f64, tip: Option<f64>, pull_back: f64) -> DeviceSdf {
|
||||||
|
let (c, s) = (theta.cos(), theta.sin());
|
||||||
|
let len = LEN - pull_back;
|
||||||
|
let poly = (0..5)
|
||||||
|
.map(|m| {
|
||||||
|
let f = len * m as f64 / 4.0;
|
||||||
|
[P0[0] + f * c, P0[1] + f * s]
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
DeviceSdf {
|
||||||
|
// the circle far outside the domain
|
||||||
|
cyl: [-10.0, -10.0, 0.01],
|
||||||
|
cyl_cut: false,
|
||||||
|
flag_cut: false,
|
||||||
|
zc: 0.0,
|
||||||
|
span: 1.0,
|
||||||
|
r_edge: 0.0,
|
||||||
|
half: HALF,
|
||||||
|
fillet: 0.0,
|
||||||
|
tip_corner: tip,
|
||||||
|
poly,
|
||||||
|
vel: Vec::new(),
|
||||||
|
plate: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The analytic cross-section: the strip `LEN × 2 HALF`, the root's half
|
||||||
|
/// disk, less the two rounded corners' `(1 − π/4) r²` (capsule: a second
|
||||||
|
/// half disk instead of the flat end).
|
||||||
|
fn analytic(tip: Option<f64>) -> (f64, f64) {
|
||||||
|
let pi = std::f64::consts::PI;
|
||||||
|
match tip {
|
||||||
|
Some(r) => (
|
||||||
|
LEN * 2.0 * HALF + 0.5 * pi * HALF * HALF - 2.0 * (1.0 - 0.25 * pi) * r * r,
|
||||||
|
2.0 * (LEN - r) + (2.0 * HALF - 2.0 * r) + pi * r + pi * HALF,
|
||||||
|
),
|
||||||
|
None => (
|
||||||
|
LEN * 2.0 * HALF + pi * HALF * HALF,
|
||||||
|
2.0 * LEN + 2.0 * pi * HALF,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cut(sdf: DeviceSdf, h: f64) -> (f64, f64) {
|
||||||
|
let (nx, ny, nz) = ((0.46 / h).ceil() as usize, (0.26 / h).ceil() as usize, 2);
|
||||||
|
let g = Grid::cubic(nx, ny, nz, h);
|
||||||
|
let body = Body::from_sdf(move |x, y, z, _t| sdf.phi_host(x, y, z));
|
||||||
|
let geo = CutGeometry::build(&body, g, 0.0);
|
||||||
|
let depth = nz as f64 * h;
|
||||||
|
let solid = (nx * ny * nz) as f64 * h * h * h - geo.fluid_volume();
|
||||||
|
let (wall, _) = geo.wall_area_and_closure();
|
||||||
|
(solid / depth, wall / depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flat_tip_plane_passes_through_the_last_point() {
|
||||||
|
for theta in [0.0, 0.3] {
|
||||||
|
for rc in [0.0, 0.00125, 0.005, HALF] {
|
||||||
|
let s = flag(theta, Some(rc), 0.0);
|
||||||
|
let tip = *s.poly.last().unwrap();
|
||||||
|
let (c, sn) = (f64::cos(theta), f64::sin(theta));
|
||||||
|
// the face centre on the surface; 1 mm beyond it at 1 mm, inside by 1 mm
|
||||||
|
assert!(s.phi_host(tip[0], tip[1], 0.0).abs() < 1e-15);
|
||||||
|
if rc < HALF {
|
||||||
|
let beyond = s.phi_host(tip[0] + 1e-3 * c, tip[1] + 1e-3 * sn, 0.0);
|
||||||
|
assert!((beyond - 1e-3).abs() < 1e-12, "{beyond}");
|
||||||
|
}
|
||||||
|
let inside = s.phi_host(tip[0] - 1e-3 * c, tip[1] - 1e-3 * sn, 0.0);
|
||||||
|
assert!(inside < 0.0);
|
||||||
|
// the corner's apex region: the rounded corner's arc
|
||||||
|
let (px, py) = (
|
||||||
|
tip[0] - rc * c - (HALF - rc) * sn,
|
||||||
|
tip[1] - rc * sn + (HALF - rc) * c,
|
||||||
|
);
|
||||||
|
let d = 2e-3;
|
||||||
|
let (dx, dy) = ((c - sn) / 2f64.sqrt(), (sn + c) / 2f64.sqrt());
|
||||||
|
let got = s.phi_host(px + (rc + d) * dx, py + (rc + d) * dy, 0.0);
|
||||||
|
assert!((got - d).abs() < 1e-12, "corner {rc}: {got} vs {d}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flat_tip_at_half_is_the_capsule_pulled_back() {
|
||||||
|
let a = flag(0.2, Some(HALF), 0.0);
|
||||||
|
let b = flag(0.2, None, HALF);
|
||||||
|
let mut worst = 0.0f64;
|
||||||
|
for i in 0..400 {
|
||||||
|
for j in 0..200 {
|
||||||
|
let (x, y) = (0.30 + 0.0003 * i as f64, 0.10 + 0.0010 * j as f64);
|
||||||
|
let (pa, pb) = (a.phi_host(x, y, 0.0), b.phi_host(x, y, 0.0));
|
||||||
|
if pa.abs().min(pb.abs()) < 0.02 {
|
||||||
|
worst = worst.max((pa - pb).abs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!(" r_c = half vs capsule pulled back by half: max |Δφ| {worst:.2e}");
|
||||||
|
assert!(worst < 1e-12, "{worst}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flat_tip_cut_volume_and_area_converge_to_the_rounded_rectangle() {
|
||||||
|
println!(" theta tip h solid err wall err");
|
||||||
|
for theta in [0.0, 0.3] {
|
||||||
|
for tip in [None, Some(0.0), Some(0.00125), Some(0.005)] {
|
||||||
|
let (va, pa) = analytic(tip);
|
||||||
|
let mut errs = Vec::new();
|
||||||
|
for ny in [62.0, 124.0, 248.0] {
|
||||||
|
let h = 0.41 / ny;
|
||||||
|
let (v, p) = cut(flag(theta, tip, 0.0), h);
|
||||||
|
let (ev, ep) = ((v - va) / va, (p - pa) / pa);
|
||||||
|
println!(
|
||||||
|
" {theta:.1} {:>9} {h:.3e} {ev:+.3e} {ep:+.3e}",
|
||||||
|
tip.map_or("capsule".to_string(), |r| format!("{r:.5}"))
|
||||||
|
);
|
||||||
|
assert!(ev.abs() < 1e-2 && ep.abs() < 2e-2, "{ev} {ep} at h {h}");
|
||||||
|
errs.push((ev, ep));
|
||||||
|
}
|
||||||
|
// the volume converges (the rung ny 248 below ny 62's error)
|
||||||
|
assert!(errs[2].0.abs() < errs[0].0.abs(), "volume {errs:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,27 @@ const X0: f64 = 0.25;
|
|||||||
/// last 10 mm is the capsule's cap).
|
/// last 10 mm is the capsule's cap).
|
||||||
const STATIONS: usize = 35;
|
const STATIONS: usize = 35;
|
||||||
|
|
||||||
|
/// R8-h: the flat tip (`RTX_E3FSI_TIP=flat`, corner radius
|
||||||
|
/// `RTX_E3FSI_TIP_CORNER`, default 0.00125 m): the centreline gains the
|
||||||
|
/// tip node A (x 0.60) as its last station and the body's tip is flat
|
||||||
|
/// through it (`DeviceSdf::tip_corner`); unset = the capsule.
|
||||||
|
pub fn flat_tip() -> Option<f64> {
|
||||||
|
match std::env::var("RTX_E3FSI_TIP").as_deref() {
|
||||||
|
Err(_) | Ok("capsule") => None,
|
||||||
|
Ok("flat") => {
|
||||||
|
let rc = env_f("RTX_E3FSI_TIP_CORNER", 0.00125);
|
||||||
|
assert!((0.0..=HALF).contains(&rc), "RTX_E3FSI_TIP_CORNER {rc}");
|
||||||
|
Some(rc)
|
||||||
|
}
|
||||||
|
Ok(v) => panic!("RTX_E3FSI_TIP={v}: flat or capsule"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The centreline's stations (35, or 36 with the flat tip's A).
|
||||||
|
fn stations() -> usize {
|
||||||
|
STATIONS + usize::from(flat_tip().is_some())
|
||||||
|
}
|
||||||
|
|
||||||
/// The flag's structure-side bookkeeping: the centreline nodes, the wetted
|
/// The flag's structure-side bookkeeping: the centreline nodes, the wetted
|
||||||
/// edges with their reference coordinates, the tip node A.
|
/// edges with their reference coordinates, the tip node A.
|
||||||
struct Flag {
|
struct Flag {
|
||||||
@@ -82,7 +103,7 @@ impl Flag {
|
|||||||
.expect("node")
|
.expect("node")
|
||||||
.0
|
.0
|
||||||
};
|
};
|
||||||
let centre = (0..STATIONS)
|
let centre = (0..stations())
|
||||||
.map(|k| find(X0 + 0.01 * k as f64, 0.2))
|
.map(|k| find(X0 + 0.01 * k as f64, 0.2))
|
||||||
.collect();
|
.collect();
|
||||||
let interface = Interface::build(mesh);
|
let interface = Interface::build(mesh);
|
||||||
@@ -217,10 +238,10 @@ fn distribute(
|
|||||||
/// The fluid's centreline from the structure's centreline displacement `c`
|
/// The fluid's centreline from the structure's centreline displacement `c`
|
||||||
/// (2 per station) with velocities `(c − c_prev) / dt`.
|
/// (2 per station) with velocities `(c − c_prev) / dt`.
|
||||||
fn line_of(t: f64, c: &[f64], c_prev: &[f64], dt: f64) -> Line {
|
fn line_of(t: f64, c: &[f64], c_prev: &[f64], dt: f64) -> Line {
|
||||||
let pts = (0..STATIONS)
|
let pts = (0..stations())
|
||||||
.map(|k| [X0 + 0.01 * k as f64 + c[2 * k], 0.2 + c[2 * k + 1]])
|
.map(|k| [X0 + 0.01 * k as f64 + c[2 * k], 0.2 + c[2 * k + 1]])
|
||||||
.collect();
|
.collect();
|
||||||
let vel = (0..STATIONS)
|
let vel = (0..stations())
|
||||||
.map(|k| {
|
.map(|k| {
|
||||||
[
|
[
|
||||||
(c[2 * k] - c_prev[2 * k]) / dt,
|
(c[2 * k] - c_prev[2 * k]) / dt,
|
||||||
@@ -252,7 +273,7 @@ fn fsi2_on_embedded3() {
|
|||||||
|
|
||||||
let mesh = flag_mesh(35, 2);
|
let mesh = flag_mesh(35, 2);
|
||||||
let flag_geo = Flag::build(&mesh);
|
let flag_geo = Flag::build(&mesh);
|
||||||
let zero_c = vec![0.0; 2 * STATIONS];
|
let zero_c = vec![0.0; 2 * stations()];
|
||||||
let rest = line_of(0.0, &zero_c, &zero_c, 1.0);
|
let rest = line_of(0.0, &zero_c, &zero_c, 1.0);
|
||||||
// `RTX_E3FSI_LOAD=<dir>`: continue from a saved coupled state (extruded
|
// `RTX_E3FSI_LOAD=<dir>`: continue from a saved coupled state (extruded
|
||||||
// onto the full duct when the saved nz differs); `RTX_E3FSI_SAVE=<dir>`
|
// onto the full duct when the saved nz differs); `RTX_E3FSI_SAVE=<dir>`
|
||||||
@@ -381,7 +402,7 @@ fn fsi2_on_embedded3() {
|
|||||||
.collect();
|
.collect();
|
||||||
let a_dofs = flag.borrow().node_dofs(flag_geo.a_node);
|
let a_dofs = flag.borrow().node_dofs(flag_geo.a_node);
|
||||||
let extract = |s: &DynamicState| -> Vec<f64> {
|
let extract = |s: &DynamicState| -> Vec<f64> {
|
||||||
let mut c = vec![0.0; 2 * STATIONS];
|
let mut c = vec![0.0; 2 * stations()];
|
||||||
for (k, d) in centre_dofs.iter().enumerate() {
|
for (k, d) in centre_dofs.iter().enumerate() {
|
||||||
c[2 * k] = s.displacement[d[0]];
|
c[2 * k] = s.displacement[d[0]];
|
||||||
c[2 * k + 1] = s.displacement[d[1]];
|
c[2 * k + 1] = s.displacement[d[1]];
|
||||||
@@ -486,8 +507,8 @@ fn fsi2_on_embedded3() {
|
|||||||
.sqrt();
|
.sqrt();
|
||||||
println!(
|
println!(
|
||||||
" step {step} pass: |c_new − c_cand| {res:.3e}, tip cand ({:+.4e}, {:+.4e}), load flag ({:+.3}, {:+.3}) cyl ({:+.3}, {:+.3}) total ({:+.3}, {:+.3}), residual {:.1e}, fresh {}",
|
" step {step} pass: |c_new − c_cand| {res:.3e}, tip cand ({:+.4e}, {:+.4e}), load flag ({:+.3}, {:+.3}) cyl ({:+.3}, {:+.3}) total ({:+.3}, {:+.3}), residual {:.1e}, fresh {}",
|
||||||
cand[2 * STATIONS - 2],
|
cand[2 * stations() - 2],
|
||||||
cand[2 * STATIONS - 1],
|
cand[2 * stations() - 1],
|
||||||
on_flag[0],
|
on_flag[0],
|
||||||
on_flag[1],
|
on_flag[1],
|
||||||
on_cyl[0],
|
on_cyl[0],
|
||||||
|
|||||||
@@ -227,17 +227,9 @@ impl E3Fluid {
|
|||||||
}));
|
}));
|
||||||
VERSION.fetch_add(1, Ordering::AcqRel);
|
VERSION.fetch_add(1, Ordering::AcqRel);
|
||||||
let (l1, l2, l3) = (lines.clone(), lines.clone(), lines.clone());
|
let (l1, l2, l3) = (lines.clone(), lines.clone(), lines.clone());
|
||||||
let body = Body::from_sdf(move |x, y, _z, t| cylinder(x, y).min(capsule(&l1, x, y, t).0))
|
|
||||||
.with_surface_velocity(move |x, y, _z, t| {
|
|
||||||
let (df, (vx, vy)) = capsule(&l2, x, y, t);
|
|
||||||
if df <= cylinder(x, y) {
|
|
||||||
(vx, vy, 0.0)
|
|
||||||
} else {
|
|
||||||
(0.0, 0.0, 0.0)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let width = nz as f64 * h;
|
let width = nz as f64 * h;
|
||||||
let body = body.with_device_sdf(move |t| {
|
let tip_corner = super::flat_tip();
|
||||||
|
let device_sdf = move |t: f64| {
|
||||||
let line = l3.read().expect("lines").at(t);
|
let line = l3.read().expect("lines").at(t);
|
||||||
DeviceSdf {
|
DeviceSdf {
|
||||||
cyl: [CX, CY, R_CYL],
|
cyl: [CX, CY, R_CYL],
|
||||||
@@ -248,12 +240,48 @@ impl E3Fluid {
|
|||||||
r_edge: h,
|
r_edge: h,
|
||||||
half: HALF,
|
half: HALF,
|
||||||
fillet: 0.0,
|
fillet: 0.0,
|
||||||
|
tip_corner,
|
||||||
poly: line.pts,
|
poly: line.pts,
|
||||||
vel: line.vel,
|
vel: line.vel,
|
||||||
// R8-c's plate body (merged alongside): the span-uniform harness keeps the polyline.
|
// R8-c's plate body (merged alongside): the span-uniform harness keeps the polyline.
|
||||||
plate: None,
|
plate: None,
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
let body = if tip_corner.is_some() {
|
||||||
|
// R8-h: the flat tip's host φ and velocity are the device form's
|
||||||
|
// (the kernel's arithmetic), cached per thread, time and lines.
|
||||||
|
let ds = device_sdf.clone();
|
||||||
|
let sdf_at = move |t: f64| -> Arc<DeviceSdf> {
|
||||||
|
thread_local! {
|
||||||
|
static SDF: RefCell<(u64, u64, Option<Arc<DeviceSdf>>)> =
|
||||||
|
const { RefCell::new((u64::MAX, u64::MAX, None)) };
|
||||||
|
}
|
||||||
|
let ver = VERSION.load(Ordering::Acquire);
|
||||||
|
SDF.with(|cell| {
|
||||||
|
let mut c = cell.borrow_mut();
|
||||||
|
if c.0 != t.to_bits() || c.1 != ver || c.2.is_none() {
|
||||||
|
c.2 = Some(Arc::new(ds(t)));
|
||||||
|
c.0 = t.to_bits();
|
||||||
|
c.1 = ver;
|
||||||
|
}
|
||||||
|
c.2.clone().expect("sdf")
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let sdf_v = sdf_at.clone();
|
||||||
|
Body::from_sdf(move |x, y, z, t| sdf_at(t).phi_host(x, y, z))
|
||||||
|
.with_surface_velocity(move |x, y, z, t| sdf_v(t).velocity_host(x, y, z))
|
||||||
|
} else {
|
||||||
|
Body::from_sdf(move |x, y, _z, t| cylinder(x, y).min(capsule(&l1, x, y, t).0))
|
||||||
|
.with_surface_velocity(move |x, y, _z, t| {
|
||||||
|
let (df, (vx, vy)) = capsule(&l2, x, y, t);
|
||||||
|
if df <= cylinder(x, y) {
|
||||||
|
(vx, vy, 0.0)
|
||||||
|
} else {
|
||||||
|
(0.0, 0.0, 0.0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let body = body.with_device_sdf(device_sdf);
|
||||||
solver.set_moving_body(body);
|
solver.set_moving_body(body);
|
||||||
let g = Grid::cubic(nx, ny, nz, h);
|
let g = Grid::cubic(nx, ny, nz, h);
|
||||||
let mut field = Field::new(g);
|
let mut field = Field::new(g);
|
||||||
|
|||||||
Reference in New Issue
Block a user