embedded3 item 10: the apertured cut-cell wall (AM-wall) — cutwall.rs classification, apertured projection with the compatible wall flux, cut predictor (V_u = αhA, averaged mass fluxes, implicit wall shear, inertia floor), cut load route; sphere MMS CutCell ≤ GhostBinary at n 12/24 (ratio 0.96), loads 9.3/10.7 % at n 24
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-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 / Build CPU-Only (Explicit) (push) Failing after 4s
Documentation / Build API Documentation (push) Failing after 4s
CI / Format Check (push) Failing after 12s
Documentation / Build User Guide (push) Successful in 5s
CI / Build (ubuntu-latest) (push) Failing after 1m50s
CI / Clippy Check (push) Failing after 2m5s
Performance Benchmarks / Run Benchmarks (push) Successful in 2m40s
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-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 / Build CPU-Only (Explicit) (push) Failing after 4s
Documentation / Build API Documentation (push) Failing after 4s
CI / Format Check (push) Failing after 12s
Documentation / Build User Guide (push) Successful in 5s
CI / Build (ubuntu-latest) (push) Failing after 1m50s
CI / Clippy Check (push) Failing after 2m5s
Performance Benchmarks / Run Benchmarks (push) Successful in 2m40s
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
d337afa8f9
commit
0e4c97ed24
@@ -1,14 +1,20 @@
|
||||
//! embedded3 gate 9a: 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. The velocity error falls at the scheme's order,
|
||||
//! every fluid cell is divergence-free, 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).
|
||||
//! 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.
|
||||
|
||||
use rtx_cfd::solvers::incompressible::ConvectionScheme;
|
||||
use rtx_cfd::solvers::incompressible::embedded3::{Body, Field, Fluid, Grid, Parameters, Solver};
|
||||
use rtx_cfd::solvers::incompressible::embedded3::{
|
||||
Body, FaceKind, Field, Fluid, Grid, Parameters, Solver, WallScheme,
|
||||
};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
const RHO: f64 = 1.0;
|
||||
@@ -114,7 +120,7 @@ struct Measurement {
|
||||
force_cv: [f64; 3],
|
||||
}
|
||||
|
||||
fn measure(n: usize) -> Measurement {
|
||||
fn measure(n: usize, scheme: WallScheme) -> Measurement {
|
||||
let h = 1.0 / n as f64;
|
||||
let dt = 0.4 * (h * h / (4.0 * MU / RHO)).min(h);
|
||||
let mut solver = Solver::new(
|
||||
@@ -128,6 +134,7 @@ fn measure(n: usize) -> Measurement {
|
||||
corrector_steps: 2,
|
||||
tolerance: 1e-8,
|
||||
convection_scheme: ConvectionScheme::Upwind,
|
||||
wall_scheme: scheme,
|
||||
..Parameters::default()
|
||||
},
|
||||
);
|
||||
@@ -140,9 +147,10 @@ fn measure(n: usize) -> Measurement {
|
||||
let g = Grid::cubic(n, n, n, h);
|
||||
let mut f = Field::new(g);
|
||||
solver.initialize(&mut f);
|
||||
let mut last = solver.advance(&mut f, dt);
|
||||
for _ in 0..200_000 {
|
||||
let (bu, bv, bw) = (f.u.clone(), f.v.clone(), f.w.clone());
|
||||
solver.advance(&mut f, dt);
|
||||
last = solver.advance(&mut f, dt);
|
||||
let mut change = 0.0_f64;
|
||||
for (a, b) in
|
||||
f.u.iter()
|
||||
@@ -157,7 +165,6 @@ fn measure(n: usize) -> Measurement {
|
||||
}
|
||||
}
|
||||
let mask = solver.mask().expect("mask");
|
||||
use rtx_cfd::solvers::incompressible::embedded3::FaceKind;
|
||||
let (mut sq, mut vol) = (0.0, 0.0);
|
||||
let dv = h * h * h;
|
||||
for k in 0..n {
|
||||
@@ -194,21 +201,53 @@ fn measure(n: usize) -> Measurement {
|
||||
}
|
||||
}
|
||||
}
|
||||
let body = solver.body().expect("body");
|
||||
let t = solver.time();
|
||||
// The apertured divergence per unit volume, the porous surface's flux
|
||||
// through the wall included (the plain divergence on the binary wall).
|
||||
let mut max_div = 0.0_f64;
|
||||
let mut at_vol = 1.0;
|
||||
let mut sum_flux = 0.0;
|
||||
let (wall_fluxes, _) = mask.wall_flux_table(body, t);
|
||||
for k in 0..n {
|
||||
for j in 0..n {
|
||||
for i in 0..n {
|
||||
if mask.is_fluid_cell(g.cell(k, j, i)) {
|
||||
let div = (f.u[g.uface(k, j, i + 1)] - f.u[g.uface(k, j, i)]) / h
|
||||
+ (f.v[g.vface(k, j + 1, i)] - f.v[g.vface(k, j, i)]) / h
|
||||
+ (f.w[g.wface(k + 1, j, i)] - f.w[g.wface(k, j, i)]) / h;
|
||||
max_div = max_div.max(div.abs());
|
||||
let idx = g.cell(k, j, i);
|
||||
if mask.is_fluid_cell(idx) {
|
||||
let flux = (mask.a_u(g.uface(k, j, i + 1)) * f.u[g.uface(k, j, i + 1)]
|
||||
- mask.a_u(g.uface(k, j, i)) * f.u[g.uface(k, j, i)])
|
||||
* h
|
||||
* h
|
||||
+ (mask.a_v(g.vface(k, j + 1, i)) * f.v[g.vface(k, j + 1, i)]
|
||||
- mask.a_v(g.vface(k, j, i)) * f.v[g.vface(k, j, i)])
|
||||
* h
|
||||
* h
|
||||
+ (mask.a_w(g.wface(k + 1, j, i)) * f.w[g.wface(k + 1, j, i)]
|
||||
- mask.a_w(g.wface(k, j, i)) * f.w[g.wface(k, j, i)])
|
||||
* h
|
||||
* h
|
||||
+ wall_fluxes[idx];
|
||||
sum_flux += flux.abs();
|
||||
if (flux / (h * h * h)).abs() > max_div {
|
||||
max_div = (flux / (h * h * h)).abs();
|
||||
at_vol = mask.vol(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let body = solver.body().expect("body");
|
||||
let surface = mask.surface_force(body, &f, MU, solver.time(), 0.5 * h);
|
||||
println!(
|
||||
" [{scheme:?} n {n}] max div {max_div:.2e} in a cell of fluid fraction {at_vol:.3e}; Σ|flux| {sum_flux:.2e}; last step residual {:.2e}",
|
||||
last.final_residual
|
||||
);
|
||||
let surface = match scheme {
|
||||
WallScheme::GhostBinary => mask.surface_force(body, &f, MU, t, 0.5 * h),
|
||||
WallScheme::CutCell => rtx_cfd::solvers::incompressible::embedded3::SurfaceForce {
|
||||
f: mask.cut_wall_force(body, &f, MU, t).expect("cut wall"),
|
||||
samples: 0,
|
||||
skipped: 0,
|
||||
},
|
||||
};
|
||||
let (i0, i1) = (n / 8, n - n / 8);
|
||||
let src = |x: f64, y: f64, z: f64| source3(x, y, z);
|
||||
let force_cv = mask.control_volume_force(&f, dt, RHO, MU, Some(&src), (i0, i1, i0, i1, i0, i1));
|
||||
@@ -226,14 +265,21 @@ fn norm(a: [f64; 3]) -> f64 {
|
||||
(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt()
|
||||
}
|
||||
|
||||
fn ladder(resolutions: &[usize]) {
|
||||
/// 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();
|
||||
let f_scale = norm(fe);
|
||||
let fcv = [fe[0] - m[0], fe[1] - m[1], fe[2] - m[2]];
|
||||
println!(
|
||||
" exact force {fe:.5?}; momentum flux {m:.5?}; the control-volume route measures {fcv:.5?}"
|
||||
" {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)).collect();
|
||||
let ms: Vec<Measurement> = resolutions.iter().map(|&n| measure(n, scheme)).collect();
|
||||
let errors: Vec<f64> = ms.iter().map(|x| x.l2_velocity).collect();
|
||||
let mut se = Vec::new();
|
||||
let mut ce = Vec::new();
|
||||
@@ -287,15 +333,57 @@ fn ladder(resolutions: &[usize]) {
|
||||
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]);
|
||||
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]);
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user