Files
rustytorch/crates/specialized/rtx-cfd/tests/embedded3_wall_lipschitz.rs
T
Omar SobhandClaude Fable 5.1 5b1621e6ad
CI / Clippy Check (push) Failing after 3s
CI / Build (ubuntu-latest) (push) Failing after 4s
CI / Format Check (push) Failing after 4s
Performance Benchmarks / Run Benchmarks (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 6s
CI / Build CPU-Only (Explicit) (push) Failing after 1m6s
Documentation / Build API Documentation (push) Failing after 1m9s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (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
embedded3 item 11: moving bodies (end-of-step mask, fresh-cell refill, space-time cut cell: step-averaged apertures, GCL wall flux, Reynolds-transport momentum), the 3D fresh-cell falsifier (plate / circle / stadium, wall + control-volume routes) and the Lipschitz sweep; ghost wall reproduces the 2D falsifier to the digit; cut wall 5–14× smoother on the circle, gates not met (fresh cell's first step); wall.rs split (impose.rs)
Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-17 16:20:35 -05:00

101 lines
3.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! embedded3 item 11b: the wall's smoothness in the interface position.
//! The manufactured sphere is marched to steady state at `M + 1` centres
//! spaced `h/M` apart across one cell along x; at each the load error
//! `E(δ) = F(δ) F_exact(δ)` (the exact force moves with the sphere and
//! is subtracted) is measured on the scheme's route. The largest jump of
//! `E` between neighbouring positions, relative to the load, and the
//! Lipschitz quotient `|ΔE| / (Δδ |F|)` are reported for both walls.
//! Registered gate (`docs/embedded3_campaign.md` item 11): the cut wall's
//! largest neighbouring jump < 1 % of the load with a bounded quotient.
//!
//! Default run: 8 positions at n = 24 (about two minutes on the host);
//! the gated `#[ignore]` variant sweeps 40.
mod embedded3_sphere;
use embedded3_sphere::{C, exact_force_and_flux, measure};
use rtx_cfd::solvers::incompressible::embedded3::WallScheme;
fn norm(a: [f64; 3]) -> f64 {
(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt()
}
struct Sweep {
/// Largest neighbouring jump of the load error relative to the load.
max_jump: f64,
/// Largest Lipschitz quotient `|ΔE| / (Δδ |F|)` (per unit length).
max_quotient: f64,
}
fn sweep(n: usize, positions: usize, scheme: WallScheme) -> Sweep {
let h = 1.0 / n as f64;
let step = h / positions as f64;
let mut errors: Vec<[f64; 3]> = Vec::new();
let mut scale = 0.0_f64;
for m in 0..=positions {
let c = (C.0 + m as f64 * step, C.1, C.2);
let (fe, _) = exact_force_and_flux(c);
let r = measure(n, scheme, c);
let e = [
r.force_surface[0] - fe[0],
r.force_surface[1] - fe[1],
r.force_surface[2] - fe[2],
];
scale = scale.max(norm(fe));
println!(
" {scheme:?} δ = {:.4} h: F {:.5?} exact {:.5?} error {:.3e} (rel {:.3e})",
m as f64 / positions as f64,
r.force_surface,
fe,
norm(e),
norm(e) / norm(fe)
);
errors.push(e);
}
let mut max_jump = 0.0_f64;
for w in errors.windows(2) {
let d = norm([w[1][0] - w[0][0], w[1][1] - w[0][1], w[1][2] - w[0][2]]);
max_jump = max_jump.max(d / scale);
}
let max_quotient = max_jump / step;
println!(
" {scheme:?}: largest neighbouring jump {:.3e} of the load (spacing {:.3e} = h/{positions}); Lipschitz quotient {:.3e} per unit length",
max_jump, step, max_quotient
);
Sweep {
max_jump,
max_quotient,
}
}
#[test]
fn sphere_load_across_one_cell() {
let ghost = sweep(24, 8, WallScheme::GhostBinary);
let cut = sweep(24, 8, WallScheme::CutCell);
println!(
" jumps: ghost {:.3e}, cut {:.3e} ({:.1}x smaller); quotients: ghost {:.3e}, cut {:.3e}",
ghost.max_jump,
cut.max_jump,
ghost.max_jump / cut.max_jump.max(1e-300),
ghost.max_quotient,
cut.max_quotient
);
assert!(ghost.max_jump.is_finite() && cut.max_jump.is_finite());
}
#[test]
#[ignore = "item 11's gated sweep (40 positions, both walls; tens of minutes on the host)"]
fn sphere_load_lipschitz_gate() {
let ghost = sweep(24, 40, WallScheme::GhostBinary);
let cut = sweep(24, 40, WallScheme::CutCell);
println!(
" GATE: cut largest jump {:.3e} of the load (ghost {:.3e}); cut quotient {:.3e} (ghost {:.3e})",
cut.max_jump, ghost.max_jump, cut.max_quotient, ghost.max_quotient
);
assert!(
cut.max_jump < 0.01,
"cut-cell jump {:.3e} of the load",
cut.max_jump
);
}