overset mesh: the flat-tip outline (P5-3 option B) — cylinder_flag_outline_deformed_tip / cylinder_flag_patch_deformed{,_from}_tip take the tip's corner radius (t = the recorded semicircle, bit for bit; smaller = the benchmark's flat face between corner arcs sampled at the wall spacing, point count fixed across the deformation, the tip's corners kept off the hull source); pins: corner t reproduces the recorded outline, the 2.5 mm outline has the exact area excess with no collapsed spacing, the O-grid builds straight and at ± 80 mm cold and warm at ny 41/62; harness knob RTX_FSI2O_TIP_CORNER (default 0.01) on the cold and warm builds, printed in both headers; pinned-toolchain fmt on touched crates
Performance Benchmarks / Run Benchmarks (push) Failing after 10s
CI / Format Check (push) Failing after 10s
CI / Build (ubuntu-latest) (push) Failing after 10s
CI / Clippy Check (push) Failing after 10s
Documentation / Build User Guide (push) Successful in 9s
CI / Build CPU-Only (Explicit) (push) Failing after 1m13s
Documentation / Build API Documentation (push) Failing after 2m25s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (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
CI / CI Success (push) Canceled after 0s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LzcjQX7tvgn87CQCyg9Cfr
This commit is contained in:
Omar Sobh
2026-09-13 22:22:08 -05:00
co-authored by Claude Fable 5.1
parent 7224521309
commit 415c32ab04
12 changed files with 519 additions and 71 deletions
@@ -7,8 +7,8 @@
//! Reference (FEATFLOW level 6): drag 14.2929, lift 1.11905. The embedded
//! staircase measured drag 15.71 (surface) / 15.62 (CV) at ny = 41 (+10%).
use rtx_cfd::mesh::patch_gen::cylinder_flag_patch;
use rtx_cfd::mesh::PatchSide;
use rtx_cfd::mesh::patch_gen::cylinder_flag_patch;
use rtx_cfd::solvers::incompressible::{
AleBoundaries, CellClass, CurvilinearParameters, CurvilinearPisoSolver, EmbeddedParameters,
EmbeddedPisoSolver, FlowField, MomentumResidual, NormalDiffusion, OversetField,
@@ -258,17 +258,17 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
let ppmax = field.patch.p.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
let upmax = field.patch.u.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
println!(
" step {steps}: rounds {:?} converged {} stalled {} bg res {:.2e} patch div {:.2e} patch iters {} conv {} | max|p| bg {pmax:.3e} patch {ppmax:.3e} max|u| patch {upmax:.3e} | defect bg {:.2e} patch {:.2e}",
r.rounds,
r.schwarz_converged,
r.schwarz_stalled,
r.background_residual,
r.patch_max_divergence,
r.patch_poisson_iterations,
r.patch_converged,
r.background_mass_defect,
r.patch_mass_defect
);
" step {steps}: rounds {:?} converged {} stalled {} bg res {:.2e} patch div {:.2e} patch iters {} conv {} | max|p| bg {pmax:.3e} patch {ppmax:.3e} max|u| patch {upmax:.3e} | defect bg {:.2e} patch {:.2e}",
r.rounds,
r.schwarz_converged,
r.schwarz_stalled,
r.background_residual,
r.patch_max_divergence,
r.patch_poisson_iterations,
r.patch_converged,
r.background_mass_defect,
r.patch_mass_defect
);
}
if steps >= max_steps {
break;
@@ -289,15 +289,15 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
.fold(0.0_f64, |m, v| m.max(v.abs()));
if steps % 500 == 0 || !umax.is_finite() {
println!(
" ny = {ny}: step {steps} t = {:.2} s drag_cv {fx:.4} drag_wall {:.4} lift_wall {:.4} max|u| {umax:.3} rounds {:?} bg res {:.1e} patch div {:.1e} [{:.0} s]",
solver.time(),
load.total()[0],
load.total()[1],
r.rounds,
r.background_residual,
r.patch_max_divergence,
start.elapsed().as_secs_f64()
);
" ny = {ny}: step {steps} t = {:.2} s drag_cv {fx:.4} drag_wall {:.4} lift_wall {:.4} max|u| {umax:.3} rounds {:?} bg res {:.1e} patch div {:.1e} [{:.0} s]",
solver.time(),
load.total()[0],
load.total()[1],
r.rounds,
r.background_residual,
r.patch_max_divergence,
start.elapsed().as_secs_f64()
);
}
assert!(
umax.is_finite(),
@@ -356,13 +356,39 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
let pct = |b: &rtx_cfd::solvers::incompressible::ResidualBucket| 100.0 * b.fx / wall[0];
println!(
" momentum residual ny = {ny} [N/m, x / y; % of wall drag; faces evaluated/total]: solved far Σr ({:+.3e}, {:+.3e}) Σ|r| ({:.3e}, {:.3e}) {}/{} | solved near ring Σr ({:+.3e}, {:+.3e}) Σ|r| ({:.4}, {:.4}) max|r| ({:.3e}, {:.3e}) {}/{} | fringefringe ({:+.4}, {:+.4}) {:+.2}% {}/{} | fringehole ({:+.4}, {:+.4}) {:+.2}% {}/{} | holehole skipped {} (ghosts: {} cells, {} faces) | Σ|r| fringefringe ({:.4}, {:.4}) fringehole ({:.4}, {:.4}); ring total ({:+.4}, {:+.4}) {:+.2}% vs routes' ring defect {:+.4} ({:+.2}%)",
mr.solved_far.fx, mr.solved_far.fy, mr.solved_far.abs_x, mr.solved_far.abs_y, mr.solved_far.evaluated, mr.solved_far.total,
mr.solved_near.fx, mr.solved_near.fy, mr.solved_near.abs_x, mr.solved_near.abs_y, mr.solved_near.max_abs_x, mr.solved_near.max_abs_y, mr.solved_near.evaluated, mr.solved_near.total,
mr.fringe_fringe.fx, mr.fringe_fringe.fy, pct(&mr.fringe_fringe), mr.fringe_fringe.evaluated, mr.fringe_fringe.total,
mr.fringe_hole.fx, mr.fringe_hole.fy, pct(&mr.fringe_hole), mr.fringe_hole.evaluated, mr.fringe_hole.total,
mr.hole_hole_skipped, mr.hole_ghosts, mr.ghost_faces,
mr.fringe_fringe.abs_x, mr.fringe_fringe.abs_y, mr.fringe_hole.abs_x, mr.fringe_hole.abs_y,
mr.fringe_fringe.fx + mr.fringe_hole.fx, mr.fringe_fringe.fy + mr.fringe_hole.fy,
mr.solved_far.fx,
mr.solved_far.fy,
mr.solved_far.abs_x,
mr.solved_far.abs_y,
mr.solved_far.evaluated,
mr.solved_far.total,
mr.solved_near.fx,
mr.solved_near.fy,
mr.solved_near.abs_x,
mr.solved_near.abs_y,
mr.solved_near.max_abs_x,
mr.solved_near.max_abs_y,
mr.solved_near.evaluated,
mr.solved_near.total,
mr.fringe_fringe.fx,
mr.fringe_fringe.fy,
pct(&mr.fringe_fringe),
mr.fringe_fringe.evaluated,
mr.fringe_fringe.total,
mr.fringe_hole.fx,
mr.fringe_hole.fy,
pct(&mr.fringe_hole),
mr.fringe_hole.evaluated,
mr.fringe_hole.total,
mr.hole_hole_skipped,
mr.hole_ghosts,
mr.ghost_faces,
mr.fringe_fringe.abs_x,
mr.fringe_fringe.abs_y,
mr.fringe_hole.abs_x,
mr.fringe_hole.abs_y,
mr.fringe_fringe.fx + mr.fringe_hole.fx,
mr.fringe_fringe.fy + mr.fringe_hole.fy,
pct(&mr.fringe_fringe) + pct(&mr.fringe_hole),
hole.0 - ring.0,
100.0 * (hole.0 - ring.0) / wall[0],
@@ -573,15 +599,36 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
let hole_flux = sf.0 + ring_sum;
println!(
" patch momentum balance ny = {ny} [N/m x / y; {} solved cells, {} interface faces, {} wall faces]: balance residual ({:+.3e}, {:+.3e}) | flux-form force through the interface ({:.4}, {:.4}) | wall force, scheme fluxes ({:.4}, {:.4}) | wall force, surface formula ({:.4}, {:.4}) | pressure defect δP = p_ls p_face ({:+.4}, {:+.4}) [{:+.2}% of wall drag] | pieces: conv_acc ({:+.4}, {:+.4}) visc_acc ({:+.4}, {:+.4}) p_face_acc ({:+.4}, {:+.4}) visc_wall ({:+.4}, {:+.4}) p_face_wall ({:+.4}, {:+.4}) p_ls ({:+.4}, {:+.4}) | acceptor band: background hole flux {:.4} patch interface {:.4} = {:+.4} ({:+.2}%)",
pb.cells, pb.acc_faces, pb.wall_faces,
bal[0], bal[1],
ff[0], ff[1],
fw[0], fw[1],
wall[0], wall[1],
dp[0], dp[1], 100.0 * dp[0] / wall[0],
pb.conv_acc[0], pb.conv_acc[1], pb.visc_acc[0], pb.visc_acc[1], pb.p_face_acc[0], pb.p_face_acc[1],
pb.visc_wall[0], pb.visc_wall[1], pb.p_face_wall[0], pb.p_face_wall[1], pb.p_ls[0], pb.p_ls[1],
hole_flux, ff[0], hole_flux - ff[0], 100.0 * (hole_flux - ff[0]) / wall[0],
pb.cells,
pb.acc_faces,
pb.wall_faces,
bal[0],
bal[1],
ff[0],
ff[1],
fw[0],
fw[1],
wall[0],
wall[1],
dp[0],
dp[1],
100.0 * dp[0] / wall[0],
pb.conv_acc[0],
pb.conv_acc[1],
pb.visc_acc[0],
pb.visc_acc[1],
pb.p_face_acc[0],
pb.p_face_acc[1],
pb.visc_wall[0],
pb.visc_wall[1],
pb.p_face_wall[0],
pb.p_face_wall[1],
pb.p_ls[0],
pb.p_ls[1],
hole_flux,
ff[0],
hole_flux - ff[0],
100.0 * (hole_flux - ff[0]) / wall[0],
);
// `RTX_OVERSET_CFD1_SAVE=dir`: the settled fields, for offline
// diagnostics without the march (background in `FlowField::save`'s
@@ -758,7 +805,8 @@ async fn momentum_residual_vanishes_on_the_solved_faces() -> CfdResult<()> {
// prescribed velocity) reads zero, so Σ|r| = N_interface · max|r| on
// each lattice.
assert!(
(near.abs_x - mr.interface_u as f64 * near.max_abs_x).abs() <= 1e-6 * near.abs_x.max(1e-300)
(near.abs_x - mr.interface_u as f64 * near.max_abs_x).abs()
<= 1e-6 * near.abs_x.max(1e-300)
&& (near.abs_y - mr.interface_v as f64 * near.max_abs_y).abs()
<= 1e-6 * near.abs_y.max(1e-300),
"solved near: not a uniform level offset on the interface — Σ|r| ({:.4e}, {:.4e}) vs N·max|r| ({:.4e}, {:.4e}) with N = ({}, {})",