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:
Omar Sobh
2026-09-25 22:32:31 -05:00
co-authored by Claude Opus 5.5
parent 171da41ed1
commit 02ab155022
9 changed files with 401 additions and 69 deletions
@@ -227,17 +227,9 @@ impl E3Fluid {
}));
VERSION.fetch_add(1, Ordering::AcqRel);
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 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);
DeviceSdf {
cyl: [CX, CY, R_CYL],
@@ -248,12 +240,48 @@ impl E3Fluid {
r_edge: h,
half: HALF,
fillet: 0.0,
tip_corner,
poly: line.pts,
vel: line.vel,
// R8-c's plate body (merged alongside): the span-uniform harness keeps the polyline.
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);
let g = Grid::cubic(nx, ny, nz, h);
let mut field = Field::new(g);