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
89 lines
3.0 KiB
Rust
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(())
|
|
}
|