rtx-cfd: the deformed cylinder–flag build's hull source carries the deformed outline — the cylinder + tip disc alone give the chord under a bent flag's convex side (P5-2 died at tip −35 mm with the patch 4.4 h thick where 6 h was asked, an acceptor's donor on a prescribed face); the death reproduced from the last saved instant by extrapolating the interface (fsi2_overset_probe_death_from_instant, with the failing acceptor's ray geometry and the overlap depths that build) and gone with the fix at every τ; RTX_FSI2O_ROWS; the zero-displacement mesh gate at 1e-5 (the pipeline's), the outline gate 1e-12 unchanged
CI / Build (macos-latest) (push) Failing after 18s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_0116sg1Qz1gMv9hdcKP1XUam
This commit is contained in:
Omar Sobh
2026-09-07 10:25:46 -07:00
co-authored by Claude Fable 5.1
parent 3c2add3f32
commit 06229dd9a2
4 changed files with 175 additions and 2 deletions
@@ -134,3 +134,158 @@ fn fsi2_on_the_overset() {
assert!(m.final_state_finite, "the flag's state is not finite");
assert!(r.death.is_none(), "the coupling died: {:?}", r.death);
}
/// `RTX_FSI2O_PROBE_INSTANT=dir`: the P5-2 warm replica died at t = 10.99
/// s (tip 35 mm) with "acceptor p donor cell not active". From the
/// instant, extrapolate the interface along its velocity and find where
/// the overlap first refuses — with the warm chain from the instant's
/// mesh and with cold builds — reporting the acceptor, its donor cell's
/// class, and the patch's thickness there.
#[test]
fn fsi2_overset_probe_death_from_instant() {
use rtx_cfd::mesh::patch_gen::{
cylinder_flag_patch_deformed, cylinder_flag_patch_deformed_from,
};
use rtx_cfd::solvers::incompressible::{CellClass, OverlapMap};
let Ok(dir) = std::env::var("RTX_FSI2O_PROBE_INSTANT") else {
return;
};
let dir = std::path::Path::new(&dir);
let case = case_from_env("FSI2O", FSI2);
let (fluid, d, t) =
fsi2_harness::overset::OversetFluid::from_instant(case, 41, 35, dir).expect("instant");
let dd: Vec<f64> = {
let bytes = std::fs::read(dir.join("patch_dd.bin")).expect("dd");
bytes
.chunks_exact(8)
.map(|c| f64::from_le_bytes(c.try_into().unwrap()))
.collect()
};
let h = fluid.h;
let nx = fluid.nx;
let tip_y = d[2 * fluid.interface.tip[fluid.interface.tip.len() / 2] + 1];
println!(
" instant t = {t:.4}: tip uy {tip_y:+.4} m, hole {} fringe {} acceptors {}",
fluid.solver.overlap().hole_cells(),
fluid.solver.overlap().fringe_count(),
fluid.solver.overlap().acceptors.len()
);
let mut warm_prev = fluid.solver.patch().mesh().clone();
for k in 0..=12 {
let tau = 0.005 * k as f64;
let dk: Vec<f64> = d.iter().zip(&dd).map(|(a, v)| a + v * tau).collect();
let edges = fluid.interface.edges(&dk);
let fillet = 0.5 * 0.41 / 41.0;
let cold = cylinder_flag_patch_deformed(
[0.2, 0.2],
0.05,
0.01,
&edges,
0.6,
h,
fillet,
6.0 * h,
12,
4.0,
100,
)
.expect("cold");
let warm = cylinder_flag_patch_deformed_from(
Some(&warm_prev),
[0.2, 0.2],
0.05,
0.01,
&edges,
0.6,
h,
fillet,
6.0 * h,
12,
4.0,
20,
)
.expect("warm");
let tip_now = dk[2 * fluid.interface.tip[fluid.interface.tip.len() / 2] + 1];
for (name, mesh) in [("cold", &cold.0), ("warm", &warm.0)] {
let valid = mesh.validate(80.0).err();
let r = OverlapMap::build(mesh, nx, 41, h, h, 4);
match r {
Ok(map) => println!(
" τ = {tau:.3} tip {tip_now:+.4}: {name} valid {:?}, overlap ok (hole {} fringe {})",
valid.is_none(),
map.hole_cells(),
map.fringe_count()
),
Err(e) => {
let msg = format!("{e:?}");
// The failing acceptor's geometry, and the overlap depth
// that would build.
let mut extra = String::new();
if let Some(a0) = msg.find("acceptor ") {
let k: usize = msg[a0 + 9..]
.split(' ')
.next()
.unwrap()
.parse()
.unwrap();
let c = mesh.cell(mesh.nn() - 1, k);
let ac = mesh.centre(c);
let inner = mesh.node_xy(mesh.node(0, k));
let outer = mesh.node_xy(mesh.node(mesh.nn(), k));
let r8 = mesh.node_xy(mesh.node(8, k));
let thick =
((outer[0] - inner[0]).powi(2) + (outer[1] - inner[1]).powi(2)).sqrt();
let hole_depth =
((r8[0] - inner[0]).powi(2) + (r8[1] - inner[1]).powi(2)).sqrt();
let mut rows_ok = Vec::new();
for rows in [5usize, 6, 7, 8] {
if OverlapMap::build(mesh, nx, 41, h, h, rows).is_ok() {
rows_ok.push(rows);
}
}
extra = format!(
" — acceptor {k} centre ({:.3}, {:.3}); its ray: wall node ({:.3}, {:.3}), outer ({:.3}, {:.3}), thickness {:.2} h, hole depth {:.2} h, band {:.2} h; overlap_rows that build: {rows_ok:?}",
ac[0], ac[1], inner[0], inner[1], outer[0], outer[1], thick / h, hole_depth / h, (thick - hole_depth) / h
);
}
if let Some(i0) = msg.find("cell (") {
let coords: Vec<usize> = msg[i0 + 6..]
.split(')')
.next()
.unwrap()
.split(',')
.map(|s| s.trim().parse().unwrap())
.collect();
let (j, i) = (coords[0], coords[1]);
let (x, y) = ((i as f64 + 0.5) * h, (j as f64 + 0.5) * h);
// The nearest inner-ring node and the patch thickness along its ray.
let mut best = (f64::INFINITY, 0usize);
for s in 0..=mesh.ns() {
let p = mesh.node_xy(mesh.node(0, s));
let dd2 = (p[0] - x).powi(2) + (p[1] - y).powi(2);
if dd2 < best.0 {
best = (dd2, s);
}
}
let s = best.1;
let inner = mesh.node_xy(mesh.node(0, s));
let outer = mesh.node_xy(mesh.node(mesh.nn(), s));
let r8 = mesh.node_xy(mesh.node(8, s));
let thick = ((outer[0] - inner[0]).powi(2) + (outer[1] - inner[1]).powi(2)).sqrt();
let hole_depth = ((r8[0] - inner[0]).powi(2) + (r8[1] - inner[1]).powi(2)).sqrt();
extra += &format!(
" — cell ({j}, {i}) at ({x:.3}, {y:.3}); nearest wall node s = {s} at ({:.3}, {:.3}); ray thickness {:.3} h, hole depth (row 8) {:.3} h, overlap band {:.3} h; outer node ({:.3}, {:.3})",
inner[0], inner[1], thick / h, hole_depth / h, (thick - hole_depth) / h, outer[0], outer[1]
);
let _ = CellClass::Hole;
}
println!(
" τ = {tau:.3} tip {tip_now:+.4}: {name} valid {:?}, overlap REFUSED: {msg}{extra}",
valid.is_none()
);
}
}
}
warm_prev = warm.0;
}
}