From 1c981566537be1f77693655eb2488e7613eab164 Mon Sep 17 00:00:00 2001 From: Omar Sobh Date: Tue, 15 Sep 2026 11:13:55 -0500 Subject: [PATCH] =?UTF-8?q?rtx-cfd:=20the=20deformed-tip=20outline=20choos?= =?UTF-8?q?es=20the=20flat=20/=20semicircle=20branch=20against=20the=20UND?= =?UTF-8?q?EFORMED=20half-thickness=20=E2=80=94=20at=20the=20semicircle=20?= =?UTF-8?q?setting=20the=20deformed=20tip's=20radius=20breathes=20by=20nan?= =?UTF-8?q?ometres=20and=20flipped=20the=20topology=20(143=20=E2=86=92=201?= =?UTF-8?q?44=20points)=20within=20the=20first=20steps=20of=20the=20P6-b?= =?UTF-8?q?=20ny=2041=20cap-12=20release=20at=20every=20=CE=B1;=20pin:=20t?= =?UTF-8?q?he=20point=20count=20is=20fixed=20under=20=C2=B1=201e-6=20thick?= =?UTF-8?q?ness=20breathing=20at=20h=2010=20/=206.6=20mm=20and=20ny=2041,?= =?UTF-8?q?=20semicircle=20and=20flat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01YJPeT6WA2e7YvAnS875AHL --- .../specialized/rtx-cfd/src/mesh/patch_gen.rs | 13 +++-- .../tests/patch_cylinder_flag_flat_tip.rs | 53 ++++++++++++++++++- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/crates/specialized/rtx-cfd/src/mesh/patch_gen.rs b/crates/specialized/rtx-cfd/src/mesh/patch_gen.rs index c06fcd9..c0b3873 100644 --- a/crates/specialized/rtx-cfd/src/mesh/patch_gen.rs +++ b/crates/specialized/rtx-cfd/src/mesh/patch_gen.rs @@ -952,9 +952,16 @@ pub fn cylinder_flag_outline_deformed_tip( let mut pts: Vec<[f64; 2]> = Vec::new(); // 1. Top edge: from the arc's top end toward the root, along the top // polyline (given tip → root) from arclength `radius` to x = x_f. - // The corner radius: `radius` (the semicircle) or the flat tip's. - let rc = tip_corner.min(radius).max(0.0); - let flat = rc < radius - 1e-12; + // The corner radius: `radius` (the semicircle) or the flat tip's. The + // branch is chosen against the UNDEFORMED half-thickness `t`, never the + // deformed tip's `radius` (which breathes by nanometres under the FEA's + // deformation and flipped the topology at the semicircle setting). + let flat = tip_corner < t - 1e-9; + let rc = if flat { + tip_corner.min(radius).max(0.0) + } else { + radius + }; // Corner arc centres and the tangent points on the faces. let c_top = [ tc[0] - rc * axis[0] - rc * normal[0], diff --git a/crates/specialized/rtx-cfd/tests/patch_cylinder_flag_flat_tip.rs b/crates/specialized/rtx-cfd/tests/patch_cylinder_flag_flat_tip.rs index 619cc84..2988f85 100644 --- a/crates/specialized/rtx-cfd/tests/patch_cylinder_flag_flat_tip.rs +++ b/crates/specialized/rtx-cfd/tests/patch_cylinder_flag_flat_tip.rs @@ -25,7 +25,14 @@ const FILLET: f64 = 0.5 * 0.41 / 41.0; const CORNER: f64 = 0.0025; fn edges(a: f64, nx: usize, ny: usize) -> FlagEdges { + edges_breathing(a, nx, ny, 0.0) +} + +/// The flag's edges with the thickness scaled by `1 + eps` (the FEA's +/// deformed tip is never exactly `t` thick). +fn edges_breathing(a: f64, nx: usize, ny: usize, eps: f64) -> FlagEdges { let len = X1 - X0; + let th = T * (1.0 + eps); let centre = |x: f64| { let xi = (x - X0) / len; let y = a * xi * xi * (3.0 - xi) / 2.0; @@ -35,7 +42,7 @@ fn edges(a: f64, nx: usize, ny: usize) -> FlagEdges { let edge = |x: f64, side: f64| -> [f64; 2] { let (y, dy) = centre(x); let n = (1.0 + dy * dy).sqrt(); - [x - side * T * dy / n, CENTRE[1] + y + side * T / n] + [x - side * th * dy / n, CENTRE[1] + y + side * th / n] }; let m = 2 * nx; let bottom: Vec<[f64; 2]> = (0..=m) @@ -234,3 +241,47 @@ fn flat_tip_patch_builds_straight_and_bent_cold_and_warm() -> CfdResult<()> { } Ok(()) } + +/// The semicircle setting (`tip_corner = t`) must keep its topology when +/// the deformed tip is a few nanometres thicker or thinner than `t` (the +/// P6-b ny 41 cap-12 marches died at every α on "topology changed +/// 143x12 -> 144x12" within the first steps of the release — the flat / +/// semicircle branch was chosen against the DEFORMED half-thickness). +#[test] +fn semicircle_setting_keeps_its_topology_under_thickness_breathing() { + for &h in &[0.01, 0.41 / 41.0, 0.41 / 62.0] { + let n0 = outline_breathing(0.0, h, T, 0.0).len(); + for &eps in &[-1e-6, -1e-9, 1e-12, 1e-9, 1e-6, 1e-4] { + for &a in &[0.0, 1e-5, -1e-5, 0.08] { + let n = outline_breathing(a, h, T, eps).len(); + assert_eq!(n, n0, "h = {h}, eps = {eps:e}, a = {a}: {n} points vs {n0}"); + } + } + // The flat tip is insensitive too. + let f0 = outline_breathing(0.0, h, CORNER, 0.0).len(); + for &eps in &[-1e-6, 1e-6, 1e-4] { + assert_eq!( + outline_breathing(0.0, h, CORNER, eps).len(), + f0, + "flat, h = {h}, eps = {eps:e}" + ); + } + } +} + +fn outline_breathing(a: f64, h: f64, corner: f64, eps: f64) -> Vec<[f64; 2]> { + cylinder_flag_outline_deformed_tip( + CENTRE, + R, + T, + &edges_breathing(a, 35, 2, eps), + X1, + FILLET, + 3, + 16, + h, + 1.15, + corner, + ) + .0 +}