embedded3 S2-1: the traction sampler on the cut field (pressure fit excludes merged cells; sampler route in the sphere MMS and DFG drivers)
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 / Build (macos-latest) (push) Waiting to run
Performance Benchmarks / Run Benchmarks (push) Failing after 3s
CI / Build (ubuntu-latest) (push) Failing after 3s
CI / Clippy Check (push) Failing after 5s
CI / Format Check (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 5s
CI / Build CPU-Only (Explicit) (push) Failing after 1m2s
Documentation / Build API Documentation (push) Failing after 1m4s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
Omar Sobh
2026-09-17 18:47:46 -05:00
co-authored by Claude Fable 5.1
parent 32546b1222
commit 2c94ae0bb1
4 changed files with 52 additions and 8 deletions
@@ -83,7 +83,10 @@ impl Mask {
} }
} }
} }
if nodes.iter().all(|&(idx, _)| self.is_fluid_cell(idx)) { // A virtually merged small cell carries its master's pressure
// unknown, not its own: it is dropped from the fit.
let usable = |idx: usize| self.is_fluid_cell(idx) && self.master(idx).is_none();
if nodes.iter().all(|&(idx, _)| usable(idx)) {
return Some(nodes.iter().map(|&(idx, w)| w * p[idx]).sum()); return Some(nodes.iter().map(|&(idx, w)| w * p[idx]).sum());
} }
// Weighted by the z-direction weight (a z-invariant field then fits // Weighted by the z-direction weight (a z-invariant field then fits
@@ -96,7 +99,7 @@ impl Mask {
for _ in 0..dirs(ny, fy).len() * dirs(nx, fx).len() { for _ in 0..dirs(ny, fy).len() * dirs(nx, fx).len() {
let &(idx, _) = it.next().expect("node"); let &(idx, _) = it.next().expect("node");
let _ = dk; let _ = dk;
if self.is_fluid_cell(idx) { if usable(idx) {
let (k, j, i) = g.kji(idx); let (k, j, i) = g.kji(idx);
pts.push(( pts.push((
(i as f64 + 0.5) * dx, (i as f64 + 0.5) * dx,
@@ -75,7 +75,12 @@ fn dfg_3d_2z_on_the_device() {
(0.0, 0.0, 0.0) (0.0, 0.0, 0.0)
} }
}); });
solver.set_body(Body::cylinder_z(CX, CY, 0.5 * D)); // The cylinder extruded across the width, with samples for the
// traction route (S2-1).
solver.set_body(Body::extruded(
rtx_cfd::solvers::incompressible::EmbeddedBody::circle(CX, CY, 0.5 * D),
H,
));
let g = Grid::cubic(nx, ny, nz, h); let g = Grid::cubic(nx, ny, nz, h);
let mut field = Field::new(g); let mut field = Field::new(g);
// Start from the inflow profile everywhere (a faster approach to steady). // Start from the inflow profile everywhere (a faster approach to steady).
@@ -99,13 +104,18 @@ fn dfg_3d_2z_on_the_device() {
let coef = 2.0 / (RHO * U_BAR * U_BAR * D * H); let coef = 2.0 / (RHO * U_BAR * U_BAR * D * H);
let csv = std::env::var("RTX_E3_DFG_CSV").ok().map(|p| { let csv = std::env::var("RTX_E3_DFG_CSV").ok().map(|p| {
let mut f = std::fs::File::create(p).expect("csv"); let mut f = std::fs::File::create(p).expect("csv");
writeln!(f, "t,cd_wall,cl_wall,cd_cv,cl_cv,dp,residual,cg").unwrap(); writeln!(
f,
"t,cd_wall,cl_wall,cd_cv,cl_cv,dp,residual,cg,cd_sampler,cl_sampler"
)
.unwrap();
f f
}); });
let mut csv = csv; let mut csv = csv;
let sample_every = (steps / 100).max(1); let sample_every = (steps / 100).max(1);
let start = std::time::Instant::now(); let start = std::time::Instant::now();
let mut last: Option<(f64, f64, f64, f64, f64)> = None; let mut last: Option<(f64, f64, f64, f64, f64)> = None;
let mut last_sampler = (f64::NAN, f64::NAN);
let mut settled = false; let mut settled = false;
for step in 0..steps { for step in 0..steps {
let r = device.advance(dt); let r = device.advance(dt);
@@ -131,6 +141,8 @@ fn dfg_3d_2z_on_the_device() {
); );
let fcv = let fcv =
mask.control_volume_force_with_walls(&field, dt, RHO, RHO * NU, None, bx, true); mask.control_volume_force_with_walls(&field, dt, RHO, RHO * NU, None, bx, true);
let fs = mask.surface_force(body, &field, RHO * NU, t, 0.5 * h);
let (cd_s, cl_s) = (coef * fs.f[0], coef * fs.f[1]);
let zc = 0.5 * H; let zc = 0.5 * H;
let p_front = mask let p_front = mask
.pressure_at(&field.p, CX - 0.5 * D, CY, zc) .pressure_at(&field.p, CX - 0.5 * D, CY, zc)
@@ -141,7 +153,8 @@ fn dfg_3d_2z_on_the_device() {
let dp = p_front - p_back; let dp = p_front - p_back;
let (cd, cl, cd_cv, cl_cv) = (coef * fw[0], coef * fw[1], coef * fcv[0], coef * fcv[1]); let (cd, cl, cd_cv, cl_cv) = (coef * fw[0], coef * fw[1], coef * fcv[0], coef * fcv[1]);
println!( println!(
" t {t:8.4}: c_D {cd:.4} (CV {cd_cv:.4}) c_L {cl:.5} (CV {cl_cv:.5}) Δp {dp:.4} residual {:.1e} CG {} [{:.0} s]", " t {t:8.4}: c_D {cd:.4} (CV {cd_cv:.4}, sampler {cd_s:.4} skipped {}) c_L {cl:.5} (CV {cl_cv:.5}, sampler {cl_s:.5}) Δp {dp:.4} residual {:.1e} CG {} [{:.0} s]",
fs.skipped,
r.final_residual, r.final_residual,
r.poisson_iterations, r.poisson_iterations,
start.elapsed().as_secs_f64() start.elapsed().as_secs_f64()
@@ -149,11 +162,12 @@ fn dfg_3d_2z_on_the_device() {
if let Some(f) = csv.as_mut() { if let Some(f) = csv.as_mut() {
writeln!( writeln!(
f, f,
"{t:.5},{cd:.6},{cl:.6},{cd_cv:.6},{cl_cv:.6},{dp:.6},{:.3e},{}", "{t:.5},{cd:.6},{cl:.6},{cd_cv:.6},{cl_cv:.6},{dp:.6},{:.3e},{},{cd_s:.6},{cl_s:.6}",
r.final_residual, r.poisson_iterations r.final_residual, r.poisson_iterations
) )
.unwrap(); .unwrap();
} }
last_sampler = (cd_s, cl_s);
if let Some((pcd, pcl, _, _, pdp)) = last { if let Some((pcd, pcl, _, _, pdp)) = last {
let rel = ((cd - pcd) / cd) let rel = ((cd - pcd) / cd)
.abs() .abs()
@@ -180,8 +194,11 @@ fn dfg_3d_2z_on_the_device() {
} }
let (cd, cl, cd_cv, cl_cv, dp) = last.expect("samples"); let (cd, cl, cd_cv, cl_cv, dp) = last.expect("samples");
println!( println!(
" FINAL ny {ny}: c_D {cd:.4} (CV {cd_cv:.4}, routes {:.2e} apart) c_L {cl:.5} (CV {cl_cv:.5}) Δp {dp:.4} — reference c_D 6.056.25, c_L 0.0080.010, Δp 0.1650.175; {:.0} s", " FINAL ny {ny}: c_D {cd:.4} (CV {cd_cv:.4}, routes {:.2e} apart; sampler {:.4}, {:.2e} from CV) c_L {cl:.5} (CV {cl_cv:.5}, sampler {:.5}) Δp {dp:.4} — reference c_D 6.056.25, c_L 0.0080.010, Δp 0.1650.175; {:.0} s",
((cd - cd_cv) / cd).abs(), ((cd - cd_cv) / cd).abs(),
last_sampler.0,
((last_sampler.0 - cd_cv) / cd_cv).abs(),
last_sampler.1,
start.elapsed().as_secs_f64() start.elapsed().as_secs_f64()
); );
if let Some(t) = device.timers() { if let Some(t) = device.timers() {
@@ -54,8 +54,13 @@ fn ladder(resolutions: &[usize], scheme: WallScheme) -> Ladder {
mm.force_cv[1] - fcv[1], mm.force_cv[1] - fcv[1],
mm.force_cv[2] - fcv[2], mm.force_cv[2] - fcv[2],
]) / f_scale; ]) / f_scale;
let a = norm([
mm.force_sampler[0] - fe[0],
mm.force_sampler[1] - fe[1],
mm.force_sampler[2] - fe[2],
]) / f_scale;
println!( 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}", " 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} F_sampler rel {a:.3e}",
mm.l2_velocity, mm.l2_velocity,
mm.max_div, mm.max_div,
mm.ghost_correction, mm.ghost_correction,
@@ -63,6 +68,15 @@ fn ladder(resolutions: &[usize], scheme: WallScheme) -> Ladder {
mm.skipped, mm.skipped,
mm.force_cv mm.force_cv
); );
if scheme == WallScheme::CutCell {
// S2-1 (reported, not gated): the traction sampler on the cut
// field against the box route — 1.15× at n 24 on the first read
// (`docs/embedded3_campaign.md`, S2-1).
println!(
" sampler / box error ratio at n = {n}: {:.3}",
a / c.max(1e-300)
);
}
se.push(s); se.push(s);
ce.push(c); ce.push(c);
} }
@@ -107,9 +107,14 @@ pub struct Measurement {
pub l2_velocity: f64, pub l2_velocity: f64,
pub max_div: f64, pub max_div: f64,
pub ghost_correction: f64, pub ghost_correction: f64,
/// The scheme's wall route (the traction sampler on the binary wall,
/// the operator route on the cut wall).
pub force_surface: [f64; 3], pub force_surface: [f64; 3],
pub skipped: usize, pub skipped: usize,
pub force_cv: [f64; 3], pub force_cv: [f64; 3],
/// The traction sampler on the cut wall (S2-1); the wall route again
/// on the binary wall.
pub force_sampler: [f64; 3],
} }
/// March the manufactured solution with the sphere at `c` to steady state on grid `n`. /// March the manufactured solution with the sphere at `c` to steady state on grid `n`.
@@ -250,6 +255,10 @@ pub fn measure(n: usize, scheme: WallScheme, c: (f64, f64, f64)) -> Measurement
let (i0, i1) = (n / 8, n - n / 8); let (i0, i1) = (n / 8, n - n / 8);
let src = |x: f64, y: f64, z: f64| source3(x, y, z); 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)); let force_cv = mask.control_volume_force(&f, dt, RHO, MU, Some(&src), (i0, i1, i0, i1, i0, i1));
let force_sampler = match scheme {
WallScheme::GhostBinary => surface.f,
WallScheme::CutCell => mask.surface_force(body, &f, MU, t, 0.5 * h).f,
};
Measurement { Measurement {
l2_velocity: (sq / vol).sqrt(), l2_velocity: (sq / vol).sqrt(),
max_div, max_div,
@@ -257,5 +266,6 @@ pub fn measure(n: usize, scheme: WallScheme, c: (f64, f64, f64)) -> Measurement
force_surface: surface.f, force_surface: surface.f,
skipped: surface.skipped, skipped: surface.skipped,
force_cv, force_cv,
force_sampler,
} }
} }