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
@@ -96,8 +96,35 @@ fn root_fillet() -> f64 {
/// 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
/// 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 {
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).
@@ -491,6 +518,7 @@ fn flag_wake_on_the_device() {
r_edge,
half: FLAG_HALF,
fillet: r_fillet,
tip_corner: flat_tip(),
poly: match (&plate, recorded()) {
(Some(_), _) => Vec::new(),
(None, Some(rec)) => recorded_polyline(rec, t)
@@ -527,9 +555,9 @@ fn flag_wake_on_the_device() {
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
// 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))
.with_surface_velocity(move |x, y, z, t| sdf_at(t).velocity_host(x, y, z))
} else {
@@ -587,6 +615,9 @@ fn flag_wake_on_the_device() {
g.cells(),
(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") };
let mut device = DeviceStep::new(solver, g);
device.upload(&field);