Files
rustytorch/crates/specialized/rtx-cfd/tests/patch_stadium.rs
T
Omar SobhandClaude Fable 5.1 e2edff9b1d
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (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
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
rtx-cfd: overset A-P3 — the falsifier plate on the overset (FAILS the registered gates by one order less than the staircase); wall force; composite pressure-level pin; Schwarz stall detection
patch_gen::{stadium, graded_fractions}: the falsifier plate as a stadium O-grid
(semicircular ends r = half-thickness; 16 cells per end arc, straights graded
0.30 h -> h at 1.15, offset 6 h, 12 rows stretched 4x; 148x12 cells, every ray
a normal, worst non-orthogonality 4 deg). CurvilinearPisoSolver::surface_force
(+ PatchLoad): F = sum(-p_f S_f + mu (grad u + grad u^T)_f . S_f) on the wall
faces with the wall cell's LSQ gradients (wall Dirichlet in the velocity fit);
HELD on the phantom circle against the exact stress integral: 1.3e-2 / 6.3e-3 /
3.6e-3 at n = 32/64/128 (orders 1.05 / 0.81), 22x the staircase's accuracy.
OversetPisoSolver: the composite p' level pinned to zero mean over the active
cells every round (the coupled problem is pure Neumann; the temporal warm start
handed each step's level to the next — background pressure 1e7 growing 5e4 per
step on the falsifier; an unpinned level also inflated the relative Schwarz
stop); stall detection (no progress over three rounds = the inner solvers'
noise floor; 6560 of 150k steps burned the 20-round cap at n = 64, a 7.5 h
n = 128 march); schwarz_stalled in the result.

tests/overset_falsifier.rs (records; RTX_OVERSET_FALSIFIER_STRICT asserts the
registered gates, _LADDER runs dt/2 and dt/4, _TRACE the top-12 spike steps):
max spike 594 / 981 / 1720 N/m at dt / dt/2 / dt/4 (staircase 6490 / 12600 /
25600), rms spike 61-89 (810), far probe 502-1509 (7900), KE injection 0.16-0.21
J/m per event on the common cell set (2.6) — every large spike a ~104-cell
full-row reclassification; exponent -0.77 (-1.0). The registered 5% gate (8.75
N/m) is missed 68x: the overset's own reclassification impulse is the finding
(omni-cortex overset_metal_campaign.md §5.10); P3b = locate per cell, then the
fringe flux balance. tests/patch_stadium.rs, curvilinear_loads.rs,
overset_common::plate_patch.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
2026-09-05 04:32:43 -07:00

89 lines
3.0 KiB
Rust

//! 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(())
}