//! A-P3 (`docs/overset_metal_campaign.md` §5.10): the stadium O-grid around //! the falsifier's plate — closed seam, positive areas, orthogonal rays, //! the spacings the time step depends on, and the overlap classification //! against the falsifier's 152² background with both donor invariants. mod overset_common; use overset_common::plate_patch; use rtx_cfd::CfdResult; use rtx_cfd::mesh::PatchMesh; use rtx_cfd::solvers::incompressible::OverlapMap; use rtx_cfd::solvers::incompressible::overset::overlap::DEFAULT_OVERLAP_ROWS; const N: usize = 152; fn spacings(mesh: &PatchMesh) -> (f64, f64, f64, f64) { let (mut min_s, mut max_s, mut min_n, mut max_n) = (f64::INFINITY, 0.0_f64, f64::INFINITY, 0.0_f64); for c in 0..mesh.cell_count() { for (f, _) in mesh.cell_faces(c) { let face = &mesh.faces()[f]; let len = (face.s[0] * face.s[0] + face.s[1] * face.s[1]).sqrt(); if mesh.is_sface(f) { // an s-face's length is the n-spacing min_n = min_n.min(len); max_n = max_n.max(len); } else { min_s = min_s.min(len); max_s = max_s.max(len); } } } (min_s, max_s, min_n, max_n) } #[test] fn stadium_patch_is_closed_orthogonal_and_resolves_the_plate() -> CfdResult<()> { let h = 1.0 / N as f64; let mesh = plate_patch([0.5, 0.5], h)?; mesh.validate(30.0).map_err(rtx_cfd::CfdError::mesh)?; let (min_s, max_s, min_n, max_n) = spacings(&mesh); // Largest interior non-orthogonality. let mut worst = 0.0_f64; for f in mesh.faces() { if f.owner.is_some() && f.neigh.is_some() { let len = (f.s[0] * f.s[0] + f.s[1] * f.s[1]).sqrt(); let dl = (f.d[0] * f.d[0] + f.d[1] * f.d[1]).sqrt(); let cos = ((f.s[0] * f.d[0] + f.s[1] * f.d[1]) / (len * dl)).clamp(-1.0, 1.0); worst = worst.max(cos.acos().to_degrees()); } } println!( " stadium patch {}x{} ({} cells): s-spacing [{:.2}, {:.2}] h, n-spacing [{:.2}, {:.2}] h, worst non-orthogonality {worst:.1} deg", mesh.ns(), mesh.nn(), mesh.cell_count(), min_s / h, max_s / h, min_n / h, max_n / h ); assert!(worst < 20.0, "non-orthogonality {worst:.1} deg"); assert!( max_n < 1.2 * h, "outer spacing {:.2} h exceeds the background's", max_n / h ); Ok(()) } #[test] fn stadium_patch_classifies_the_falsifier_background() -> CfdResult<()> { let h = 1.0 / N as f64; for cy in [0.5, 0.58, 0.42] { let mesh = plate_patch([0.5, cy], h)?; let map = OverlapMap::build(&mesh, N, N, h, h, DEFAULT_OVERLAP_ROWS)?; println!( " plate at y = {cy}: hole {}, fringe {}, prescribed u {} v {}, acceptors {}", map.hole_cells(), map.fringe_count(), map.fringe_u.len(), map.fringe_v.len(), map.acceptors.len() ); assert!(map.fringe_count() > 0); } Ok(()) }