rtx-cfd: overset A-P2 — the patch overlaps the background (OversetPisoSolver), gated S1–S5
CI / Test (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 / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
Documentation / Build User Guide (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
Documentation / Build API Documentation (push) Canceled after 0s

Background = the embedded solver with a mask from the overlap classification
(embedded/{mod,projection}.rs: module split, projection's solve/apply halves,
set_overlap, fringe p' Dirichlet by elimination into extra_diag/rhs, anchor
dropped, set_inner_stop_factor, phase API begin_step/solve_correction/
apply_correction/end_step; advance rebuilt on the phases — every suite digit-
identical, FSI2 default line-for-line). Patch = the curvilinear solver with an
acceptor ring (set_side_velocity; set_acceptor_ring/stamp_acceptors/
set_acceptor_correction; acceptor Dirichlet by elimination into
PressureSystem.links so the BiCGSTAB stop stays in flux units — identity rows
measured unconverged at 2431 iterations; same phase API). overset/overlap.rs:
OverlapMap — hole/fringe/active from the patch's own indices (hole = body or
k <= nn-1-overlap_rows, DEFAULT_OVERLAP_ROWS = 4 from the 2.9 h depth budget),
dual-quad inverse-bilinear donors patch→fringe, lattice donors →acceptors,
both invariants asserted, mass-defect measures. overset/mod.rs:
OversetPisoSolver — advance (exchange rebuilt BEFORE the predictors from the
previous corrected field), alternating Schwarz on the acceptor p' vector with
Anderson(3) (plain Schwarz measured 0.82/round: floating patch, Neumann wall)
and the previous step's vector as warm start (1 round/corrector at steady
state), stop relative to the STEP's p' scale (the MG absolute stop is
1e-9/dt² in pressure — the whole second correction), set_patch_mesh,
snapshot/restore carrying the warm-start vector.

Gates: overlap linear-exact 1e-13, quadratic orders 1.96/1.99 (acceptors),
1.40/1.91 (fringe); half-couplings: patch with exact acceptors Stokes 2.07/1.98
+ 2.08/1.98, upwind 0.84/0.84, background with exact fringe 7.86e-3/2.90e-3/
1.09e-3 (1.44/1.41); two-mesh MMS n=32/64: background 8.717e-3/4.207e-3 (1.03x/
0.97x the embedded circle), patch 1.322e-2/6.904e-3 (1.5-1.6x), orders 1.05/
0.94, patch div <= 5e-13, overlap mass defect 3.6e-3 -> 8.2e-4 of the overlap
flux (under the registered 1e-3 from n=64; disclosed at 32); motion: stationary
patch through set_patch_mesh bit-identical, snapshot/restore with a pending mesh
bit-identical, translating phantom circle 1.22x/1.19x the static level over
4.5 cells. Inherited, disclosed: poisson_equivalence's no-body multigrid pin
fails by 3.9e-9 at d46fb0b (M1's commit; verified in a clean worktree).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
This commit is contained in:
Omar Sobh
2026-09-04 19:24:13 -07:00
co-authored by Claude Fable 5.1
parent d46fb0b7a7
commit afd1bff6ee
14 changed files with 3523 additions and 482 deletions
@@ -0,0 +1,88 @@
//! A-P2, step S4 (`docs/overset_metal_campaign.md` §5.9): the two-mesh
//! manufactured solution on the static overset.
//!
//! Unit square (closed box, exact Dirichlet velocity on every side, the
//! `mms_piso.rs` field and source, upwind) with the phantom circle at
//! (0.6, 0.45) r = 0.2 as the patch's inner wall (the exact velocity as its
//! Dirichlet), the skewed stretched annulus to r = 0.354 as the patch
//! (outer spacing = h), background n = 32 / 64 / 128. Gates: the
//! background's L2 on its fluid faces within 1.5× the embedded-circle
//! ladder (8.489e-3 / 4.341e-3 at n = 32 / 64) and first order to 128; the
//! patch's L2 on its interior cells ≤ 2× the background's at equal h and
//! first order; both divergence-free on their equation-carrying cells;
//! Schwarz rounds per corrector recorded; the overlap mass defect per step,
//! both sides, relative to the overlap flux scale — measured first.
mod overset_common;
use overset_common::{march, orders};
use rtx_cfd::CfdResult;
#[tokio::test]
async fn two_mesh_mms_recovers_the_single_mesh_levels_on_both_meshes() -> CfdResult<()> {
let reference = [8.489e-3, 4.341e-3];
let only: Option<usize> = std::env::var("RTX_OVERSET_N")
.ok()
.and_then(|v| v.parse().ok());
let tol: f64 = std::env::var("RTX_OVERSET_SCHWARZ")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1e-3);
let mut bg = Vec::new();
let mut patch = Vec::new();
for (idx, n) in [32usize, 64, 128].into_iter().enumerate() {
if only.is_some_and(|o| o != n) {
continue;
}
let m = march(n, tol).await?;
println!(
" overset n={n}: L2 background {:.6e} patch {:.6e} ({} steps); Schwarz rounds mean {:.2} max {} (failures {}); \
worst bg residual {:.2e}, patch div {:.2e}; overlap mass defect (of the overlap flux) at steady state bg {:.3e} patch {:.3e}, worst over the transient bg {:.3e} patch {:.3e}",
m.l2_background,
m.l2_patch,
m.steps,
m.mean_rounds,
m.max_rounds,
m.schwarz_failures,
m.worst_bg_residual,
m.worst_patch_div_rel,
m.final_defect_bg_rel,
m.final_defect_patch_rel,
m.worst_defect_bg_rel,
m.worst_defect_patch_rel
);
if let Some(r) = reference.get(idx) {
assert!(
m.l2_background < 1.5 * r,
"n = {n}: background L2 {:.3e} > 1.5x embedded circle {r:.3e}",
m.l2_background
);
}
assert!(
m.l2_patch < 2.0 * m.l2_background,
"n = {n}: patch L2 {:.3e} > 2x background {:.3e}",
m.l2_patch,
m.l2_background
);
assert!(
m.worst_patch_div_rel < 1e-9,
"patch divergence {:.3e}",
m.worst_patch_div_rel
);
bg.push(m.l2_background);
patch.push(m.l2_patch);
}
if only.is_none() {
let (ob, op) = (orders(&bg), orders(&patch));
println!(" overset orders: background {ob:?}, patch {op:?}");
assert!(
ob.iter().all(|&x| (0.75..1.3).contains(&x)),
"background orders {ob:?}"
);
assert!(
op.iter().all(|&x| (0.7..1.6).contains(&x)),
"patch orders {op:?}"
);
}
Ok(())
}