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
Co-Authored-By: Claude Fable 5.1 <[email protected]>
101 lines
3.5 KiB
Rust
101 lines
3.5 KiB
Rust
//! 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
|
||
);
|
||
}
|