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
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:
co-authored by
Claude Fable 5.1
parent
3c2add3f32
commit
06229dd9a2
@@ -669,7 +669,14 @@ pub fn cylinder_flag_patch_deformed_from(
|
|||||||
) -> CfdResult<(PatchMesh, (usize, f64))> {
|
) -> CfdResult<(PatchMesh, (usize, f64))> {
|
||||||
let (inner, tip) =
|
let (inner, tip) =
|
||||||
cylinder_flag_outline_deformed(centre, r, t, edges, x_tip_ref, fillet, 3, 16, h, 1.15);
|
cylinder_flag_outline_deformed(centre, r, t, edges, x_tip_ref, fillet, 3, 16, h, 1.15);
|
||||||
let hull_src = hull_source(centre, r, tip.centre, tip.radius, tip.axis);
|
// The hull source carries the deformed outline itself: for a straight
|
||||||
|
// flag the cylinder and the tip disc alone give the flag's surfaces as
|
||||||
|
// the hull's tangents, for a BENT flag they give the chord under the
|
||||||
|
// convex side — measured on FSI2 at tip −35 mm as a patch 4.4 h thick
|
||||||
|
// where 6 h was asked, the acceptors' donors reaching into the fringe
|
||||||
|
// (P5-2's first death).
|
||||||
|
let mut hull_src = hull_source(centre, r, tip.centre, tip.radius, tip.axis);
|
||||||
|
hull_src.extend(inner.iter().copied());
|
||||||
let start = prev.and_then(|m| {
|
let start = prev.and_then(|m| {
|
||||||
(m.ns() == inner.len() && m.nn() == nn).then(|| {
|
(m.ns() == inner.len() && m.nn() == nn).then(|| {
|
||||||
(0..=nn)
|
(0..=nn)
|
||||||
|
|||||||
@@ -164,7 +164,11 @@ fn zero_displacement_reproduces_the_rigid_patch() -> CfdResult<()> {
|
|||||||
worst_outline < 1e-12,
|
worst_outline < 1e-12,
|
||||||
"outline differs by {worst_outline:.3e}"
|
"outline differs by {worst_outline:.3e}"
|
||||||
);
|
);
|
||||||
assert!(worst < 1e-6, "worst centre distance {worst:.3e}");
|
// 2.5e-8 with the hull source of the rigid build; 3.5e-6 (3.5e-4 h)
|
||||||
|
// once the deformed outline joins the hull source (near-collinear
|
||||||
|
// extra vertices change the offset polyline's point set) — the
|
||||||
|
// outline gate above is the construction's, this one the pipeline's.
|
||||||
|
assert!(worst < 1e-5, "worst centre distance {worst:.3e}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -310,6 +310,13 @@ impl OversetFluid {
|
|||||||
let params = OversetParameters {
|
let params = OversetParameters {
|
||||||
stall_rounds: 2,
|
stall_rounds: 2,
|
||||||
max_rounds,
|
max_rounds,
|
||||||
|
// `RTX_FSI2O_ROWS`: the overlap depth (patch rows kept non-hole
|
||||||
|
// below the acceptor row; default 4). A deeper overlap widens
|
||||||
|
// the band the acceptors' donors need on a bent patch.
|
||||||
|
overlap_rows: std::env::var("RTX_FSI2O_ROWS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(OversetParameters::default().overlap_rows),
|
||||||
..OversetParameters::default()
|
..OversetParameters::default()
|
||||||
};
|
};
|
||||||
let mut solver = OversetPisoSolver::new(background, patch, (nx, ny, h, h), params)?;
|
let mut solver = OversetPisoSolver::new(background, patch, (nx, ny, h, h), params)?;
|
||||||
|
|||||||
@@ -134,3 +134,158 @@ fn fsi2_on_the_overset() {
|
|||||||
assert!(m.final_state_finite, "the flag's state is not finite");
|
assert!(m.final_state_finite, "the flag's state is not finite");
|
||||||
assert!(r.death.is_none(), "the coupling died: {:?}", r.death);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user