rtx-cfd: cylinder_flag_patch_deformed_from — the warm-started regeneration (P5's cost lever): the previous patch's interior and its outer ring projected onto the new offset polygon as the start, and an IDEMPOTENT build (one Winslow sweep alternating with the ray re-spacing; the P4-0 one-shot is not a fixed point — measured as the interior moving 0.05 h per build at rest), so a base converged as that map's fixed point stays put (9e-11 h at rest) and each build tracks the outline (interior/wall motion 0.99–1.14, worst angle 74.7°, wall row 0.37 h) 4.5× faster than the cold build; the rigid generator untouched (pins pass); fsi2 overset harness: RTX_FSI2O_WARM_SWEEPS with the converged base as the initial patch and the committed step's mesh as each step's warm start
Documentation / Build User Guide (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (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 / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
CI / Python Bindings (maturin) (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
Performance Benchmarks / Run Benchmarks (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 09:10:05 -07:00
co-authored by Claude Fable 5.1
parent 62f1d4bf9b
commit 3a68048934
4 changed files with 326 additions and 33 deletions
+125 -18
View File
@@ -589,7 +589,16 @@ pub fn cylinder_flag_patch(
// 1.74 → 1.31 with fillet = h/2 on the ny = 41/62/82 ladder).
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)
o_grid_from_outline(
inner,
hull_src,
h,
offset,
nn,
stretch,
winslow_sweeps,
None,
)
}
/// The FEA flag's wetted edges, deformed: `bottom` from the root to the
@@ -622,11 +631,62 @@ pub fn cylinder_flag_patch_deformed(
nn: usize,
stretch: f64,
winslow_sweeps: usize,
) -> CfdResult<(PatchMesh, (usize, f64))> {
cylinder_flag_patch_deformed_from(
None,
centre,
r,
t,
edges,
x_tip_ref,
h,
fillet,
offset,
nn,
stretch,
winslow_sweeps,
)
}
/// [`cylinder_flag_patch_deformed`] warm-started from `prev` (the same
/// topology: its interior rows are the Winslow start instead of the
/// transfinite one, so a few sweeps suffice when the outline moved a
/// little — P5's per-pass regeneration). `None` is the cold build.
#[allow(clippy::too_many_arguments)]
pub fn cylinder_flag_patch_deformed_from(
prev: Option<&PatchMesh>,
centre: [f64; 2],
r: f64,
t: f64,
edges: &FlagEdges,
x_tip_ref: f64,
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, x_tip_ref, 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)
let start = prev.and_then(|m| {
(m.ns() == inner.len() && m.nn() == nn).then(|| {
(0..=nn)
.map(|k| (0..=m.ns()).map(|i| m.node_xy(m.node(k, i))).collect())
.collect::<Vec<Vec<[f64; 2]>>>()
})
});
o_grid_from_outline(
inner,
hull_src,
h,
offset,
nn,
stretch,
winslow_sweeps,
start,
)
}
/// The deformed flag's rounded tip: the semicircle's centre, radius and
@@ -857,6 +917,7 @@ fn hull_source(
/// 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.
#[allow(clippy::too_many_arguments)]
fn o_grid_from_outline(
mut inner: Vec<[f64; 2]>,
hull_src: Vec<[f64; 2]>,
@@ -865,6 +926,7 @@ fn o_grid_from_outline(
nn: usize,
stretch: f64,
winslow_sweeps: usize,
start: Option<Vec<Vec<[f64; 2]>>>,
) -> CfdResult<(PatchMesh, (usize, f64))> {
inner.reverse(); // clockwise
let ns = inner.len();
@@ -892,23 +954,68 @@ fn o_grid_from_outline(
.collect();
let mut inner_closed = inner.clone();
inner_closed.push(inner[0]);
// Transfinite start.
// Transfinite start, or the previous mesh's interior with the new
// inner and outer rings (the warm start).
let eta = stretched_fractions(nn, stretch);
let mut x: Vec<Vec<[f64; 2]>> = eta
.iter()
.map(|&e| {
(0..=ns)
.map(|i| {
[
(1.0 - e) * inner_closed[i][0] + e * outer[i][0],
(1.0 - e) * inner_closed[i][1] + e * outer[i][1],
]
})
.collect()
})
.collect();
let report = winslow_smooth(&mut x, ns, 1e-10 * h, winslow_sweeps, Some(&outer_poly));
respace_rays(&mut x, ns, &eta);
let warm = start.is_some();
let mut x: Vec<Vec<[f64; 2]>> = match start {
Some(mut rows) => {
rows[0] = inner_closed.clone();
// The outer ring warm-starts too: the previous ring's nodes
// projected onto the new offset polygon keep the sliding
// rows' converged distribution (the crude normal push would
// undo it every build — measured as the interior moving
// 0.05 h with the wall at rest).
rows[nn] = rows[nn]
.iter()
.map(|&p| nearest_on_polyline(&outer_poly, p))
.collect();
rows
}
None => eta
.iter()
.map(|&e| {
(0..=ns)
.map(|i| {
[
(1.0 - e) * inner_closed[i][0] + e * outer[i][0],
(1.0 - e) * inner_closed[i][1] + e * outer[i][1],
]
})
.collect()
})
.collect(),
};
// The cold build: Winslow, then the ray re-spacing (the P4-0 one-shot).
// The warm build must be IDEMPOTENT at rest, and the one-shot is not
// (the re-spaced mesh is not the smoother's fixed point: measured as
// the interior moving 0.05 h per build with the wall at rest), so it
// alternates one sweep with a re-spacing — the base converged as a
// fixed point of that map stays put, and a moving outline is tracked.
let report = if warm {
let mut last = f64::INFINITY;
let mut done = 0;
for k in 0..winslow_sweeps {
let before = x.clone();
winslow_smooth(&mut x, ns, 0.0, 1, Some(&outer_poly));
respace_rays(&mut x, ns, &eta);
last = x
.iter()
.zip(&before)
.flat_map(|(r, b)| r.iter().zip(b))
.map(|(p, q)| ((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2)).sqrt())
.fold(0.0, f64::max);
done = k + 1;
if last < 1e-10 * h {
break;
}
}
(done, last)
} else {
let report = winslow_smooth(&mut x, ns, 1e-10 * h, winslow_sweeps, Some(&outer_poly));
respace_rays(&mut x, ns, &eta);
report
};
let mut xs = Vec::with_capacity((nn + 1) * (ns + 1));
let mut ys = Vec::with_capacity(xs.capacity());
for row in &x {