Files
OneCAD-Simulation/crates/lbm-gpu/tests/throughput.rs
T
Christian Svensson b123f31d47 lbm-gpu: the lattice Boltzmann step on the GPU, checked against the CPU
A new L7 crate: the CPU scheme's collision (TRT with Guo forcing), the
per-link streaming rules (bounce-back with Ladd's term, slip reflection,
open placeholders), the Zou–He boundaries and the momentum exchange per
force group as wgpu compute shaders, stepping the flow fieldcraft_lbm's
Flow prepared, through its Backend trait: populations stay on the device,
a chunk of steps is one submission, and only frames, forces and the
stability check come back. Populations are stored as deviations from
their weights, which keeps single precision honest: the verification
cases (Poiseuille with Λ = 3/16, Couette with wall forces, Taylor–Green,
a sphere's drag, a Zou–He channel) agree with the CPU to 2–5e-6 in
velocity, 1e-6 in density and 5e-5 in force, with those tolerances stated
in the tests. The rules are applied one thread per link rather than in a
per-cell loop, which lavapipe's JIT miscompiles (and which diverges on
real GPUs anyway). Native only for now; the browser registers nothing.

Part of #529.
2026-10-17 07:23:40 -04:00

57 lines
2.1 KiB
Rust

//! Throughput of the GPU backend (ignored; run with
//! `cargo test --release -p fieldcraft-lbm-gpu --test throughput -- --ignored --nocapture`),
//! on the cases of `fieldcraft-lbm`'s throughput test.
#[path = "../../lbm/tests/common/mod.rs"]
mod common;
use common::*;
use fieldcraft_gpu::select::Policy;
use fieldcraft_lbm::backend::Backend;
use fieldcraft_lbm_gpu::GpuFlow;
use fieldcraft_model::{BoxFace, FaceCondition};
#[test]
#[ignore]
fn cell_updates_per_second() {
for (name, l, sphere_radius) in [("periodic box", 64usize, 0.0), ("periodic box", 128, 0.0), ("box with a sphere", 128, 32.0)] {
let side = l as f64;
let mut d = domain([0.0; 3], [side; 3], 1.0);
for f in BoxFace::ALL {
*d.faces.get_mut(f) = FaceCondition::Periodic {};
}
d.acceleration = [1e-6, 0.0, 0.0];
let mut obstacles = Vec::new();
if sphere_radius > 0.0 {
d.obstacles = vec!["Ball".into()];
obstacles.push(obstacle("Ball", sphere([side / 2.0; 3], sphere_radius, 4), &["skin"]));
}
let steps = 200;
let m = model(
1.0,
1.0 / 6.0,
d,
fieldcraft_model::Procedure::Fluid {
domain: "Flow".into(),
end_time: None,
steps: Some(steps),
time_step: Some(1.0),
frames: 1,
magic: None,
},
);
let (_, flow) = prepare(&m, &obstacles);
let Ok(mut gpu) = GpuFlow::new(&flow, Policy::from_env().expect("FIELDCRAFT_GPU names a policy")) else {
println!("skipping: no GPU");
return;
};
// One warm-up chunk, then the timed run: the chunk's steps are one submission.
gpu.advance(1).unwrap();
let start = std::time::Instant::now();
gpu.advance(steps).unwrap();
let _ = gpu.mass().unwrap();
let s = start.elapsed().as_secs_f64();
println!("{name}, {l}³ on {}: {:.1} M cell updates/s", gpu.adapter_name(), (gpu.fluid_cells() as f64 * steps as f64) / s / 1e6);
}
}