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
@@ -56,6 +56,27 @@ const X0: f64 = 0.25;
/// last 10 mm is the capsule's cap).
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
/// edges with their reference coordinates, the tip node A.
struct Flag {
@@ -82,7 +103,7 @@ impl Flag {
.expect("node")
.0
};
let centre = (0..STATIONS)
let centre = (0..stations())
.map(|k| find(X0 + 0.01 * k as f64, 0.2))
.collect();
let interface = Interface::build(mesh);
@@ -217,10 +238,10 @@ fn distribute(
/// The fluid's centreline from the structure's centreline displacement `c`
/// (2 per station) with velocities `(c − c_prev) / dt`.
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]])
.collect();
let vel = (0..STATIONS)
let vel = (0..stations())
.map(|k| {
[
(c[2 * k] - c_prev[2 * k]) / dt,
@@ -252,7 +273,7 @@ fn fsi2_on_embedded3() {
let mesh = flag_mesh(35, 2);
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);
// `RTX_E3FSI_LOAD=<dir>`: continue from a saved coupled state (extruded
// onto the full duct when the saved nz differs); `RTX_E3FSI_SAVE=<dir>`
@@ -381,7 +402,7 @@ fn fsi2_on_embedded3() {
.collect();
let a_dofs = flag.borrow().node_dofs(flag_geo.a_node);
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() {
c[2 * k] = s.displacement[d[0]];
c[2 * k + 1] = s.displacement[d[1]];
@@ -486,8 +507,8 @@ fn fsi2_on_embedded3() {
.sqrt();
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 {}",
cand[2 * STATIONS - 2],
cand[2 * STATIONS - 1],
cand[2 * stations() - 2],
cand[2 * stations() - 1],
on_flag[0],
on_flag[1],
on_cyl[0],