#![cfg(feature = "cuda")]
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use rustsim_crowd::common::{Pedestrian, WallSegment};
use rustsim_crowd::cuda::social_force as sfm_cuda;
use rustsim_crowd::social_force;
fn seed_counterflow(n: usize, length: f64, width: f64, seed: u64) -> Vec<Pedestrian> {
let mut rng = StdRng::seed_from_u64(seed);
(0..n)
.map(|i| {
let x: f64 = rng.gen_range(0.0..length);
let y: f64 = rng.gen_range(0.0..width);
let dir = if i % 2 == 0 { 1.0 } else { -1.0 };
let dest_x = if dir > 0.0 { length + 1000.0 } else { -1000.0 };
Pedestrian::new([x, y], [dir * 1.0, 0.0], 0.25, 1.34, [dest_x, y])
})
.collect()
}
fn run_soak(n: usize, length: f64, width: f64, ticks: usize, seed: u64) {
let peds_initial = seed_counterflow(n, length, width, seed);
let walls = vec![
WallSegment {
a: [0.0, -0.5],
b: [length, -0.5],
},
WallSegment {
a: [0.0, width + 0.5],
b: [length, width + 0.5],
},
];
let params = social_force::Params::default();
let dt = 0.05;
let mut resident = match sfm_cuda::CudaResident::upload(&peds_initial, &walls) {
Ok(r) => r,
Err(e) => {
eprintln!("skipping cuda soak test (n={n}, ticks={ticks}): no CUDA device ({e})");
return;
}
};
let cutoff = social_force::neighbor_cutoff(¶ms);
let drift = params.max_speed * dt * ticks as f64 + 10.0;
let origin = [-drift, -(width * 0.5 + 5.0)];
let extent_x = length + 2.0 * drift;
let extent_y = width + 2.0 * (width * 0.5 + 5.0);
let cfg = sfm_cuda::GridConfig {
origin,
cell_size: cutoff,
dims: (
(extent_x / cutoff).ceil() as u32 + 1,
(extent_y / cutoff).ceil() as u32 + 1,
),
cutoff_sq: cutoff * cutoff,
};
resident
.enable_grid(cfg)
.expect("enable_grid failed on soak scene");
for t in 0..ticks {
resident
.step_grid(¶ms, dt)
.unwrap_or_else(|e| panic!("step_grid failed at tick {t}: {e}"));
}
let mut peds: Vec<Pedestrian> = Vec::with_capacity(n);
resident.download(&mut peds).expect("download failed");
assert_eq!(peds.len(), n, "population drifted during soak");
let speed_ceiling = params.max_speed * 1.5;
for (i, p) in peds.iter().enumerate() {
assert!(
p.pos[0].is_finite() && p.pos[1].is_finite(),
"agent {i} position not finite after soak: {:?}",
p.pos
);
assert!(
p.vel[0].is_finite() && p.vel[1].is_finite(),
"agent {i} velocity not finite after soak: {:?}",
p.vel
);
let speed = (p.vel[0] * p.vel[0] + p.vel[1] * p.vel[1]).sqrt();
assert!(
speed <= speed_ceiling,
"agent {i} speed {speed:.3} m/s exceeds max_speed*1.5={speed_ceiling:.3}"
);
}
}
#[test]
fn cuda_grid_soak_mid_scale() {
run_soak(10_000, 200.0, 40.0, 300, 0xC0FFEE);
}
#[test]
#[ignore]
fn cuda_grid_soak_one_million_short() {
run_soak(1_000_000, 2000.0, 400.0, 10, 0xBADC0DE);
}