Files
rustytorch/crates/specialized/rtx-cfd/tests/embedded3_device_moving.rs
T
Omar SobhandClaude Fable 5.1 aa096465a6
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 / Build (macos-latest) (push) Waiting to run
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
Documentation / Build API Documentation (push) Failing after 4s
CI / Build CPU-Only (Explicit) (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 5s
CI / Format Check (push) Failing after 13s
CI / Clippy Check (push) Failing after 45s
CI / Build (ubuntu-latest) (push) Failing after 2m1s
Performance Benchmarks / Run Benchmarks (push) Successful in 2m35s
embedded3 S2-2a/S2-3: moving bodies on the device (phased tables, host rebuild per step, impose kernel; gate test), the per-span cut wall route, the flag-wake driver and its geometry pre-flight
Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-17 18:53:00 -05:00

119 lines
4.3 KiB
Rust

//! embedded3 S2-2a: a moving body on the device — the circle of the
//! falsifier (R 0.05 across a periodic 4-cell slab on h = 1/152, at 1 m/s
//! peak) on the cut wall, host vs device over 100 steps under tight
//! tolerances to `1e-9·scale`; the host rebuild's share of the step time
//! recorded.
//!
//! `RTX_CUDA_ARCH=sm_120 cargo test --release -p rtx-cfd --features cuda --test embedded3_device_moving -- --nocapture`
#![cfg(feature = "cuda")]
use rtx_cfd::solvers::incompressible::ConvectionScheme;
use rtx_cfd::solvers::incompressible::embedded3::step::device::DeviceStep;
use rtx_cfd::solvers::incompressible::embedded3::{
Body, Boundaries, Field, Fluid, Grid, Parameters, Side, Solver, WallScheme,
};
const N: usize = 152;
const AMP: f64 = 0.08;
const U: f64 = 1.0;
fn circle(moving: bool) -> Body {
let yc = move |t: f64| {
if moving {
0.5 + AMP * (U / AMP * t).sin()
} else {
0.5
}
};
let vc = move |t: f64| if moving { U * (U / AMP * t).cos() } else { 0.0 };
Body::from_sdf(move |x, y, _z, t| ((x - 0.5_f64).powi(2) + (y - yc(t)).powi(2)).sqrt() - 0.05)
.with_surface_velocity(move |_, _, _, t| (0.0, vc(t), 0.0))
}
fn make(tight: bool) -> (Solver, Grid) {
let (tolerance, inner_stop_factor) = if tight { (1e-12, 1e-6) } else { (1e-8, 1e-2) };
let mut solver = Solver::new(
Fluid {
density: 1000.0,
viscosity: 1.0,
reference_velocity: 1.0,
reference_length: 0.1,
},
Parameters {
corrector_steps: 2,
tolerance,
inner_stop_factor,
convection_scheme: ConvectionScheme::Upwind,
wall_scheme: WallScheme::CutCell,
boundaries: Boundaries {
z0: Side::Periodic,
z1: Side::Periodic,
..Boundaries::default()
},
..Parameters::default()
},
);
solver.set_boundary_velocity(|_, _, _, _| (0.0, 0.0, 0.0));
solver.set_moving_body(circle(true));
(solver, Grid::cubic(N, N, 4, 1.0 / N as f64))
}
fn max_diff(a: &[f64], b: &[f64]) -> f64 {
a.iter()
.zip(b)
.fold(0.0_f64, |m, (&x, &y)| m.max((x - y).abs()))
}
fn scale(a: &[f64]) -> f64 {
a.iter().fold(0.0_f64, |m, &x| m.max(x.abs()))
}
fn march(tight: bool, steps: usize, bound: Option<f64>) {
let dt = 3.24e-4;
let (mut host, g) = make(tight);
let mut fh = Field::new(g);
host.initialize(&mut fh);
let (mut ds, _) = make(tight);
let mut fd = Field::new(g);
ds.initialize(&mut fd);
unsafe { std::env::set_var("RTX_PROFILE", "1") };
let mut device = DeviceStep::new(ds, g);
device.upload(&fd);
let start = std::time::Instant::now();
let (mut differ, mut fresh_h, mut fresh_d) = (0, 0, 0);
for _ in 0..steps {
let rh = host.advance(&mut fh, dt);
let rd = device.advance(dt);
if rh.corrector_steps_performed != rd.corrector_steps_performed {
differ += 1;
}
fresh_h += rh.fresh_cells;
fresh_d += rd.fresh_cells;
}
let seconds = start.elapsed().as_secs_f64();
device.download(&mut fd);
let du = max_diff(&fh.u, &fd.u)
.max(max_diff(&fh.v, &fd.v))
.max(max_diff(&fh.w, &fd.w));
let su = scale(&fh.u).max(scale(&fh.v)).max(scale(&fh.w));
let dp = max_diff(&fh.p, &fd.p);
let sp = scale(&fh.p).max(1000.0);
let t = device.timers().expect("timers");
println!(
" moving circle 152²×4 CutCell (tight {tight}): {steps} steps; host vs device max |Δu| {du:.3e} on {su:.3e}, max |Δp| {dp:.3e} on {sp:.3e}; fresh cells host {fresh_h} device {fresh_d}; corrector counts differ on {differ} steps; {:.1} ms per step (host + device), device step {:.1} ms of which rebuild {:.1} ms",
1e3 * seconds / steps as f64,
1e-6 * (t.predictor_ns + t.poisson_ns + t.apply_ns + t.transfer_ns) as f64 / steps as f64,
1e-6 * t.transfer_ns as f64 / steps as f64
);
assert_eq!(fresh_h, fresh_d, "fresh-cell counts differ");
if let Some(b) = bound {
assert!(du < b * su, "velocity differs: {du:.3e} on {su:.3e}");
assert!(dp < b * sp, "pressure differs: {dp:.3e} on {sp:.3e}");
}
}
#[test]
fn moving_circle_host_equals_device() {
march(false, 100, None);
march(true, 100, Some(1e-9));
}