Files
rustytorch/crates/specialized/rtx-cfd/tests/embedded3_embedded_mms.rs
T
Omar SobhandClaude Fable 5.1 5b1621e6ad
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
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
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

145 lines
4.9 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 gates 9a and 10: the manufactured solution with an embedded
//! sphere (centre (0.6, 0.45, 0.5), r 0.2, off-centre so the exact force is
//! not zero by symmetry) carrying the exact field as its surface velocity,
//! on the binary ghost wall (item 9) and the apertured cut-cell wall (item
//! 10). The velocity error falls at the scheme's order, every fluid cell
//! is divergence-free (apertured, with the porous surface's flux, on the
//! cut wall), the compatibility correction shrinks, and both load routes
//! converge to the exact surface integral of the manufactured stress (the
//! control-volume route measures F M with M the momentum flux through
//! the porous manufactured surface). Item 10's gate: the cut wall's errors
//! are at most the binary wall's at every n, its loads within 10 % at the
//! finest rung.
mod embedded3_sphere;
use embedded3_sphere::{C, Measurement, 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()
}
/// The velocity errors and the two routes' relative force errors per rung.
struct Ladder {
errors: Vec<f64>,
surface: Vec<f64>,
cv: Vec<f64>,
}
fn ladder(resolutions: &[usize], scheme: WallScheme) -> Ladder {
let (fe, m) = exact_force_and_flux(C);
let f_scale = norm(fe);
let fcv = [fe[0] - m[0], fe[1] - m[1], fe[2] - m[2]];
println!(
" {scheme:?}: exact force {fe:.5?}; momentum flux {m:.5?}; the control-volume route measures {fcv:.5?}"
);
let ms: Vec<Measurement> = resolutions.iter().map(|&n| measure(n, scheme, C)).collect();
let errors: Vec<f64> = ms.iter().map(|x| x.l2_velocity).collect();
let mut se = Vec::new();
let mut ce = Vec::new();
for (k, (mm, &n)) in ms.iter().zip(resolutions).enumerate() {
let rate = if k == 0 {
" -".to_string()
} else {
format!("{:5.2}", (errors[k - 1] / errors[k]).log2())
};
let s = norm([
mm.force_surface[0] - fe[0],
mm.force_surface[1] - fe[1],
mm.force_surface[2] - fe[2],
]) / f_scale;
let c = norm([
mm.force_cv[0] - fcv[0],
mm.force_cv[1] - fcv[1],
mm.force_cv[2] - fcv[2],
]) / f_scale;
println!(
" n = {n:3} L2 u {:.4e} (order {rate}) max div {:.2e} ghost corr {:.2e} F_surface {:.4?} rel {s:.3e} (skipped {}) F_cv {:.4?} rel {c:.3e}",
mm.l2_velocity,
mm.max_div,
mm.ghost_correction,
mm.force_surface,
mm.skipped,
mm.force_cv
);
se.push(s);
ce.push(c);
}
assert!(
errors.windows(2).all(|w| w[1] < w[0]),
"errors not monotone {errors:?}"
);
for w in errors.windows(2) {
let rate = (w[0] / w[1]).log2();
assert!(
rate > 0.75 && rate < 2.3,
"order {rate:.3} outside [0.75, 2.3]"
);
}
for mm in &ms {
assert!(mm.max_div < 1e-5, "max div {:.3e}", mm.max_div);
}
assert!(
se.windows(2).all(|w| w[1] < w[0]),
"surface-route error not falling {se:?}"
);
assert!(
ce.windows(2).all(|w| w[1] < w[0]),
"control-volume-route error not falling {ce:?}"
);
Ladder {
errors,
surface: se,
cv: ce,
}
}
/// Item 10's comparison: the cut wall's velocity error at most the binary
/// wall's at every rung; both routes within `load_bound` at the finest.
fn compare(resolutions: &[usize], load_bound: f64) {
let ghost = ladder(resolutions, WallScheme::GhostBinary);
let cut = ladder(resolutions, WallScheme::CutCell);
for (k, &n) in resolutions.iter().enumerate() {
println!(
" n = {n:3} L2 u ghost {:.4e} cut {:.4e} (ratio {:.3})",
ghost.errors[k],
cut.errors[k],
cut.errors[k] / ghost.errors[k]
);
assert!(
cut.errors[k] <= ghost.errors[k],
"cut-cell error above the binary wall's at n = {n}"
);
}
let last = resolutions.len() - 1;
assert!(
cut.surface[last] < load_bound && cut.cv[last] < load_bound,
"cut-cell loads at the finest rung: surface {:.3e}, control volume {:.3e} (bound {load_bound})",
cut.surface[last],
cut.cv[last]
);
}
#[test]
fn embedded_sphere_recovers_the_manufactured_solution() {
ladder(&[12, 24], WallScheme::GhostBinary);
}
#[test]
fn cut_cell_wall_recovers_the_manufactured_solution() {
compare(&[12, 24], 0.2);
}
#[test]
#[ignore = "the three-rung ladder to n = 48 (minutes on the host)"]
fn embedded_sphere_three_rungs() {
ladder(&[12, 24, 48], WallScheme::GhostBinary);
}
#[test]
#[ignore = "item 10's finest rung: the cut wall's loads within 10 % at n = 48"]
fn cut_cell_three_rungs() {
compare(&[12, 24, 48], 0.1);
}