rtx-cfd: P5-0 — cylinder_flag_patch_deformed / cylinder_flag_outline_deformed: the O-grid around the cylinder and the FEA's deformed flag edges (FlagEdges bottom / tip / top), edges resampled at the rigid outline's graded arclength spacing, root fillets from the rigid construction, the tip semicircle from the deformed tip's centre and axis; the O-grid body factored out (o_grid_from_outline, hull_source) and the rigid generator kept bit-identical (its pins pass); gates: zero displacement reproduces the rigid outline to 6e-17 (mesh 2.5e-8 through the hull/Winslow pipeline, recorded), ±80 mm cantilever shapes valid at 80° with wall row 0.43 h, along-body 0.196 h, wall nodes on the edges to 6e-17, ny = 41 overlap builds
CI / CI Success (push) Canceled after 0s
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
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (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 (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
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (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
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
cee9adef26
commit
c2451fbacf
@@ -587,22 +587,277 @@ pub fn cylinder_flag_patch(
|
||||
// The fillet radius is a GEOMETRY parameter, fixed across a refinement
|
||||
// ladder (a radius ∝ h is an O(h) boundary error: measured Stokes orders
|
||||
// 1.74 → 1.31 with fillet = h/2 on the ny = 41/62/82 ladder).
|
||||
let mut inner = cylinder_flag_outline(centre, r, t, x_tip, fillet, 3, 16, h, 1.15);
|
||||
let inner = cylinder_flag_outline(centre, r, t, x_tip, fillet, 3, 16, h, 1.15);
|
||||
let hull_src = hull_source(centre, r, [x_tip - t, centre[1]], t, [1.0, 0.0]);
|
||||
o_grid_from_outline(inner, hull_src, h, offset, nn, stretch, winslow_sweeps)
|
||||
}
|
||||
|
||||
/// The FEA flag's wetted edges, deformed: `bottom` from the root to the
|
||||
/// tip, `tip` from the bottom corner to the top corner, `top` from the
|
||||
/// tip back to the root (the `Interface` walk's order).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FlagEdges {
|
||||
/// Root → tip.
|
||||
pub bottom: Vec<[f64; 2]>,
|
||||
/// Bottom corner → top corner.
|
||||
pub tip: Vec<[f64; 2]>,
|
||||
/// Tip → root.
|
||||
pub top: Vec<[f64; 2]>,
|
||||
}
|
||||
|
||||
/// The O-grid around the cylinder and a DEFORMED flag (P5-0, §5.12): the
|
||||
/// inner ring from [`cylinder_flag_outline_deformed`], everything else as
|
||||
/// [`cylinder_flag_patch`]; with undeformed edges the two agree to
|
||||
/// rounding.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn cylinder_flag_patch_deformed(
|
||||
centre: [f64; 2],
|
||||
r: f64,
|
||||
t: f64,
|
||||
edges: &FlagEdges,
|
||||
h: f64,
|
||||
fillet: f64,
|
||||
offset: f64,
|
||||
nn: usize,
|
||||
stretch: f64,
|
||||
winslow_sweeps: usize,
|
||||
) -> CfdResult<(PatchMesh, (usize, f64))> {
|
||||
let (inner, tip) = cylinder_flag_outline_deformed(centre, r, t, edges, fillet, 3, 16, h, 1.15);
|
||||
let hull_src = hull_source(centre, r, tip.centre, tip.radius, tip.axis);
|
||||
o_grid_from_outline(inner, hull_src, h, offset, nn, stretch, winslow_sweeps)
|
||||
}
|
||||
|
||||
/// The deformed flag's rounded tip: the semicircle's centre, radius and
|
||||
/// outward axis.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TipArc {
|
||||
/// Centre of the semicircle.
|
||||
pub centre: [f64; 2],
|
||||
/// Radius (half the tip edge's length, ≈ the half-thickness).
|
||||
pub radius: f64,
|
||||
/// Unit vector from the centre through the arc's apex (the flag's
|
||||
/// tangent at the tip).
|
||||
pub axis: [f64; 2],
|
||||
}
|
||||
|
||||
/// Cumulative arclength of an OPEN polyline (`cum[i]` at `pts[i]`).
|
||||
fn open_cum(pts: &[[f64; 2]]) -> Vec<f64> {
|
||||
let mut cum = Vec::with_capacity(pts.len());
|
||||
let mut s = 0.0;
|
||||
cum.push(0.0);
|
||||
for w in pts.windows(2) {
|
||||
s += ((w[1][0] - w[0][0]).powi(2) + (w[1][1] - w[0][1]).powi(2)).sqrt();
|
||||
cum.push(s);
|
||||
}
|
||||
cum
|
||||
}
|
||||
|
||||
/// Point at arclength `s` along an open polyline.
|
||||
fn open_point_at(pts: &[[f64; 2]], cum: &[f64], s: f64) -> [f64; 2] {
|
||||
let n = pts.len();
|
||||
let s = s.clamp(0.0, cum[n - 1]);
|
||||
let mut i = 0;
|
||||
while i + 2 < n && cum[i + 1] < s {
|
||||
i += 1;
|
||||
}
|
||||
let seg = cum[i + 1] - cum[i];
|
||||
let t = if seg > 0.0 { (s - cum[i]) / seg } else { 0.0 };
|
||||
[
|
||||
pts[i][0] + t * (pts[i + 1][0] - pts[i][0]),
|
||||
pts[i][1] + t * (pts[i + 1][1] - pts[i][1]),
|
||||
]
|
||||
}
|
||||
|
||||
/// The sub-polyline of an open polyline between arclengths `s0 < s1`,
|
||||
/// with interpolated end points.
|
||||
fn open_slice(pts: &[[f64; 2]], cum: &[f64], s0: f64, s1: f64) -> Vec<[f64; 2]> {
|
||||
let mut out = vec![open_point_at(pts, cum, s0)];
|
||||
for (i, c) in cum.iter().enumerate() {
|
||||
if *c > s0 && *c < s1 {
|
||||
out.push(pts[i]);
|
||||
}
|
||||
}
|
||||
out.push(open_point_at(pts, cum, s1));
|
||||
out
|
||||
}
|
||||
|
||||
/// Arclength at which an open polyline first crosses `x = x0` (searched
|
||||
/// from the end `from_end` — the root end of a flag edge), linear on the
|
||||
/// crossing segment.
|
||||
fn arclength_at_x(pts: &[[f64; 2]], cum: &[f64], x0: f64, from_end: bool) -> f64 {
|
||||
let n = pts.len();
|
||||
let order: Vec<usize> = if from_end {
|
||||
(0..n - 1).rev().collect()
|
||||
} else {
|
||||
(0..n - 1).collect()
|
||||
};
|
||||
for i in order {
|
||||
let (a, b) = (pts[i], pts[i + 1]);
|
||||
if (a[0] - x0) * (b[0] - x0) <= 0.0 && a[0] != b[0] {
|
||||
let f = (x0 - a[0]) / (b[0] - a[0]);
|
||||
return cum[i] + f * (cum[i + 1] - cum[i]);
|
||||
}
|
||||
}
|
||||
if from_end {
|
||||
cum[n - 1]
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Points at graded arclength fractions along an open polyline from its
|
||||
/// start, EXCLUDING the end point (the rigid outline's convention).
|
||||
fn graded_along(pts: &[[f64; 2]], d0: f64, d_straight: f64, grade: f64) -> Vec<[f64; 2]> {
|
||||
let cum = open_cum(pts);
|
||||
let len = cum[cum.len() - 1];
|
||||
let fr = graded_fractions(len, d0, d_straight, grade);
|
||||
fr[..fr.len() - 1]
|
||||
.iter()
|
||||
.map(|&f| open_point_at(pts, &cum, f * len))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The deformed Turek–Hron body outline (counter-clockwise, starting at
|
||||
/// the tip arc's top end, the order of [`cylinder_flag_outline`]): the top
|
||||
/// edge along the deformed top polyline from the tip arc to the root
|
||||
/// fillet's tangent point (resampled at the graded arclength spacing), the
|
||||
/// root fillets and the cylinder arc from the RIGID construction (the
|
||||
/// clamp keeps the root straight to a micron), the bottom edge, and the
|
||||
/// tip semicircle whose centre and axis come from the deformed tip. Returns
|
||||
/// the outline and the tip arc.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn cylinder_flag_outline_deformed(
|
||||
centre: [f64; 2],
|
||||
r: f64,
|
||||
t: f64,
|
||||
edges: &FlagEdges,
|
||||
fillet: f64,
|
||||
k_fillet: usize,
|
||||
k_tip: usize,
|
||||
d_straight: f64,
|
||||
grade: f64,
|
||||
) -> (Vec<[f64; 2]>, TipArc) {
|
||||
let (cx, cy) = (centre[0], centre[1]);
|
||||
// Root junction, as the rigid outline.
|
||||
let x_f = cx + ((r + fillet).powi(2) - (t + fillet).powi(2)).sqrt();
|
||||
let f_top = [x_f, cy + t + fillet];
|
||||
let f_bot = [x_f, cy - t - fillet];
|
||||
let dir_top = [
|
||||
(f_top[0] - cx) / (r + fillet),
|
||||
(f_top[1] - cy) / (r + fillet),
|
||||
];
|
||||
let t2_top = [cx + r * dir_top[0], cy + r * dir_top[1]];
|
||||
let dir_bot = [
|
||||
(f_bot[0] - cx) / (r + fillet),
|
||||
(f_bot[1] - cy) / (r + fillet),
|
||||
];
|
||||
let t2_bot = [cx + r * dir_bot[0], cy + r * dir_bot[1]];
|
||||
let d0 = (PI * t / k_tip as f64).min(fillet * PI / 2.0 / k_fillet as f64);
|
||||
// The tip: corners, the tangent axis from the last segments, the
|
||||
// semicircle of radius half the tip edge, centred `radius` back.
|
||||
let (bottom, top) = (&edges.bottom, &edges.top);
|
||||
let (nb, nt) = (bottom.len(), top.len());
|
||||
let (b, tc) = (bottom[nb - 1], top[0]);
|
||||
let (bp, tp) = (bottom[nb - 2], top[1]);
|
||||
let ax = [
|
||||
(b[0] - bp[0]) + (tc[0] - tp[0]),
|
||||
(b[1] - bp[1]) + (tc[1] - tp[1]),
|
||||
];
|
||||
let al = (ax[0] * ax[0] + ax[1] * ax[1]).sqrt().max(1e-300);
|
||||
let axis = [ax[0] / al, ax[1] / al];
|
||||
let normal = [-axis[1], axis[0]]; // left of the axis = the top side
|
||||
let radius = 0.5 * ((tc[0] - b[0]).powi(2) + (tc[1] - b[1]).powi(2)).sqrt();
|
||||
let mid = [0.5 * (b[0] + tc[0]), 0.5 * (b[1] + tc[1])];
|
||||
let tip_c = [mid[0] - radius * axis[0], mid[1] - radius * axis[1]];
|
||||
let start_top = [tip_c[0] + radius * normal[0], tip_c[1] + radius * normal[1]];
|
||||
let start_bot = [tip_c[0] - radius * normal[0], tip_c[1] - radius * normal[1]];
|
||||
|
||||
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.
|
||||
let cum_t = open_cum(top);
|
||||
let s_root_t = arclength_at_x(top, &cum_t, x_f, true);
|
||||
let mut top_run = open_slice(top, &cum_t, radius, s_root_t);
|
||||
top_run[0] = start_top;
|
||||
pts.extend(graded_along(&top_run, d0, d_straight, grade));
|
||||
// 2. Top fillet, 3. cylinder arc, 4. bottom fillet — the rigid construction.
|
||||
let a_t1 = -PI / 2.0;
|
||||
let mut a_t2 = (t2_top[1] - f_top[1]).atan2(t2_top[0] - f_top[0]);
|
||||
while a_t2 - a_t1 > PI {
|
||||
a_t2 -= 2.0 * PI;
|
||||
}
|
||||
while a_t2 - a_t1 < -PI {
|
||||
a_t2 += 2.0 * PI;
|
||||
}
|
||||
pts.extend(arc_points(f_top, fillet, a_t1, a_t2, k_fillet));
|
||||
let th_top = (t2_top[1] - cy).atan2(t2_top[0] - cx);
|
||||
let th_bot = (t2_bot[1] - cy).atan2(t2_bot[0] - cx) + 2.0 * PI;
|
||||
let arc_len = (th_bot - th_top) * r;
|
||||
let k_arc = ((arc_len / d_straight).round() as usize).max(8);
|
||||
pts.extend(arc_points(centre, r, th_top, th_bot, k_arc));
|
||||
let a_b2 = (t2_bot[1] - f_bot[1]).atan2(t2_bot[0] - f_bot[0]);
|
||||
let mut a_b1 = PI / 2.0;
|
||||
while a_b1 - a_b2 > PI {
|
||||
a_b1 -= 2.0 * PI;
|
||||
}
|
||||
while a_b1 - a_b2 < -PI {
|
||||
a_b1 += 2.0 * PI;
|
||||
}
|
||||
pts.extend(arc_points(f_bot, fillet, a_b2, a_b1, k_fillet));
|
||||
// 5. Bottom edge: from x = x_f along the bottom polyline (root → tip)
|
||||
// to `radius` short of the corner, ending at the arc's bottom end.
|
||||
let cum_b = open_cum(bottom);
|
||||
let s_root_b = arclength_at_x(bottom, &cum_b, x_f, false);
|
||||
let mut bot_run = open_slice(bottom, &cum_b, s_root_b, cum_b[nb - 1] - radius);
|
||||
let last = bot_run.len() - 1;
|
||||
bot_run[last] = start_bot;
|
||||
pts.extend(graded_along(&bot_run, d0, d_straight, grade));
|
||||
// 6. Tip semicircle from the bottom end through the apex to the top end.
|
||||
let a0 = (start_bot[1] - tip_c[1]).atan2(start_bot[0] - tip_c[0]);
|
||||
pts.extend(arc_points(tip_c, radius, a0, a0 + PI, k_tip));
|
||||
(
|
||||
pts,
|
||||
TipArc {
|
||||
centre: tip_c,
|
||||
radius,
|
||||
axis,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// The hull source points: the cylinder sampled finely, the tip
|
||||
/// semicircle (centre, radius, outward axis) sampled finely, and its two
|
||||
/// end points.
|
||||
fn hull_source(
|
||||
centre: [f64; 2],
|
||||
r: f64,
|
||||
tip_c: [f64; 2],
|
||||
rt: f64,
|
||||
axis: [f64; 2],
|
||||
) -> Vec<[f64; 2]> {
|
||||
let mut hull_src: Vec<[f64; 2]> = arc_points(centre, r, 0.0, 2.0 * PI, 256);
|
||||
let a = axis[1].atan2(axis[0]);
|
||||
hull_src.extend(arc_points(tip_c, rt, a - PI / 2.0, a + PI / 2.0, 64));
|
||||
let normal = [-axis[1], axis[0]];
|
||||
hull_src.push([tip_c[0] + rt * normal[0], tip_c[1] + rt * normal[1]]);
|
||||
hull_src.push([tip_c[0] - rt * normal[0], tip_c[1] - rt * normal[1]]);
|
||||
hull_src
|
||||
}
|
||||
|
||||
/// The O-grid body shared by the rigid and the deformed generators: the
|
||||
/// inner ring (counter-clockwise in, reversed to clockwise), the outer
|
||||
/// ring on the hull offset, transfinite start, Winslow, re-spacing.
|
||||
fn o_grid_from_outline(
|
||||
mut inner: Vec<[f64; 2]>,
|
||||
hull_src: Vec<[f64; 2]>,
|
||||
h: f64,
|
||||
offset: f64,
|
||||
nn: usize,
|
||||
stretch: f64,
|
||||
winslow_sweeps: usize,
|
||||
) -> CfdResult<(PatchMesh, (usize, f64))> {
|
||||
inner.reverse(); // clockwise
|
||||
let ns = inner.len();
|
||||
// Outer ring: hull offset, sampled at the inner ring's arclength
|
||||
// fractions, starting from the point nearest the inner start's normal
|
||||
// offset.
|
||||
let mut hull_src: Vec<[f64; 2]> = arc_points(centre, r, 0.0, 2.0 * PI, 256);
|
||||
hull_src.extend(arc_points(
|
||||
[x_tip - t, centre[1]],
|
||||
t,
|
||||
-PI / 2.0,
|
||||
PI / 2.0,
|
||||
64,
|
||||
));
|
||||
hull_src.push([x_tip - t, centre[1] + t]);
|
||||
hull_src.push([x_tip - t, centre[1] - t]);
|
||||
let hull = convex_hull(hull_src);
|
||||
let outer_poly = offset_convex_polygon(&hull, offset, 24);
|
||||
// Initial outer ring: the inner point pushed along its outward normal
|
||||
|
||||
Reference in New Issue
Block a user