//! 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 = 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 ); println!( " pressure: L2 (mean-shifted) background {:.3e} patch {:.3e}; level offset between the meshes {:+.3e}; largest fringe-vs-neighbours jump {:.3e} of the exact range", m.l2_p_background, m.l2_p_patch, m.p_offset_between_meshes, m.p_fringe_jump_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(()) }