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
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

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
This commit is contained in:
Omar Sobh
2026-09-05 04:32:43 -07:00
co-authored by Claude Fable 5.1
parent afd1bff6ee
commit e2edff9b1d
8 changed files with 943 additions and 2 deletions
@@ -148,3 +148,98 @@ pub fn channel_varying_skew(
}
PatchMesh::from_nodes(nx, ny, x, y, periodic_x.then_some([lx, 0.0]))
}
/// Spacings along a straight of `length` that grow geometrically from `d0`
/// at both ends (ratio `ratio`) to at most `d1` in the middle, symmetric;
/// returns the cumulative fractions `0 ..= 1`. The count adapts to the
/// length.
pub fn graded_fractions(length: f64, d0: f64, d1: f64, ratio: f64) -> Vec<f64> {
// One end's graded run, until the spacing reaches d1 or half the length.
let mut run = Vec::new();
let mut d = d0;
let mut acc = 0.0;
while d < d1 && acc + d < 0.5 * length {
run.push(d);
acc += d;
d *= ratio;
}
let middle = length - 2.0 * acc;
let n_mid = ((middle / d1).round() as usize).max(1);
let d_mid = middle / n_mid as f64;
let mut spacings = run.clone();
spacings.extend(std::iter::repeat_n(d_mid, n_mid));
spacings.extend(run.iter().rev());
let total: f64 = spacings.iter().sum();
let mut fr = Vec::with_capacity(spacings.len() + 1);
let mut s = 0.0;
fr.push(0.0);
for w in &spacings {
s += w;
fr.push(s / total);
}
let last = fr.len() - 1;
fr[last] = 1.0;
fr
}
/// An O-grid around a STADIUM (a rectangle of half-length `hx` and
/// half-thickness `r` with semicircular ends of radius `r`, the plate of
/// the fresh-cell falsifier rounded at its ends) centred at `centre`: the
/// inner ring is the stadium, the outer ring its normal offset by
/// `offset`, so every ray is a normal (orthogonal cells). Along the body
/// the straights are graded from the arc spacing `d0 = π r / k_arc` at the
/// tangent points to `d_straight` in the middle (ratio `grade`); each end
/// arc carries `k_arc` cells. Across, `nn` cells with the geometric
/// `stretch` (wall spacing = `offset · (g 1)/(g^nn 1)`). `s` runs
/// CLOCKWISE (the right-handed frame `PatchMesh` needs), starting at the
/// top-left tangent point. Periodic.
#[allow(clippy::too_many_arguments)]
pub fn stadium(
centre: [f64; 2],
hx: f64,
r: f64,
offset: f64,
k_arc: usize,
d_straight: f64,
grade: f64,
nn: usize,
stretch: f64,
) -> CfdResult<PatchMesh> {
let a = hx - r; // half-length of the straights
let d0 = PI * r / k_arc as f64;
let straight = graded_fractions(2.0 * a, d0, d_straight, grade);
let mut inner = Vec::new();
let mut outer = Vec::new();
let mut push = |p: [f64; 2], n: [f64; 2]| {
inner.push([centre[0] + p[0], centre[1] + p[1]]);
outer.push([
centre[0] + p[0] + offset * n[0],
centre[1] + p[1] + offset * n[1],
]);
};
// Top straight, left → right (clockwise around the body).
for &f in &straight[..straight.len() - 1] {
push([-a + f * 2.0 * a, r], [0.0, 1.0]);
}
// Right arc, from +90° down to 90° (exclusive of both ends' duplicates
// handled by the straights: include angles strictly between).
for k in 0..k_arc {
let th = PI / 2.0 - PI * k as f64 / k_arc as f64;
let (s, c) = th.sin_cos();
push([a + r * c, r * s], [c, s]);
}
// Bottom straight, right → left.
for &f in &straight[..straight.len() - 1] {
push([a - f * 2.0 * a, -r], [0.0, -1.0]);
}
// Left arc, from 90° down to 270°.
for k in 0..k_arc {
let th = -PI / 2.0 - PI * k as f64 / k_arc as f64;
let (s, c) = th.sin_cos();
push([-a + r * c, r * s], [c, s]);
}
// Close the ring: the last point repeats the first.
inner.push(inner[0]);
outer.push(outer[0]);
transfinite(&inner, &outer, nn, stretch, Some([0.0, 0.0]))
}