#![cfg(feature = "cuda")]
#![allow(deprecated)]
use rustsim_crowd::common::{Pedestrian, WallSegment};
use rustsim_crowd::cuda::optimal_steps as osm_cuda;
use rustsim_crowd::optimal_steps;
fn seed() -> Vec<Pedestrian> {
(0..16)
.map(|i| {
let lane = (i % 2) as f64;
let col = (i / 2) as f64;
let dir = if i % 2 == 0 { 1.0 } else { -1.0 };
Pedestrian::new(
[col * 1.5, lane * 1.5],
[0.0, 0.0],
0.25,
1.34,
[dir * 50.0, lane * 1.5],
)
})
.collect()
}
#[test]
fn cuda_osm_matches_cpu_within_tolerance() {
let mut peds_cpu = seed();
let mut peds_gpu = seed();
let walls = vec![
WallSegment {
a: [-20.0, -1.0],
b: [20.0, -1.0],
},
WallSegment {
a: [-20.0, 2.5],
b: [20.0, 2.5],
},
];
let params = optimal_steps::Params::default();
let dt = 0.4;
let state = match osm_cuda::CudaState::new() {
Ok(s) => s,
Err(e) => {
eprintln!("skipping cuda_osm_matches_cpu_within_tolerance: no CUDA device ({e})");
return;
}
};
for _ in 0..5 {
optimal_steps::step(&mut peds_cpu, &walls, ¶ms, dt);
state
.step(&mut peds_gpu, &walls, ¶ms, dt)
.expect("CUDA OSM step failed after successful init");
}
for (i, (a, b)) in peds_cpu.iter().zip(peds_gpu.iter()).enumerate() {
let dx = (a.pos[0] - b.pos[0]).abs();
let dy = (a.pos[1] - b.pos[1]).abs();
let dvx = (a.vel[0] - b.vel[0]).abs();
let dvy = (a.vel[1] - b.vel[1]).abs();
assert!(
dx < 1e-1 && dy < 1e-1,
"agent {i} position diverged: cpu={:?} gpu={:?}",
a.pos,
b.pos
);
assert!(
dvx < 1e-1 && dvy < 1e-1,
"agent {i} velocity diverged: cpu={:?} gpu={:?}",
a.vel,
b.vel
);
}
}
#[test]
fn cuda_osm_resident_matches_stateless_within_tolerance() {
let mut peds_stateless = seed();
let peds_initial = seed();
let walls = vec![
WallSegment {
a: [-20.0, -1.0],
b: [20.0, -1.0],
},
WallSegment {
a: [-20.0, 2.5],
b: [20.0, 2.5],
},
];
let params = optimal_steps::Params::default();
let dt = 0.4;
let state = match osm_cuda::CudaState::new() {
Ok(s) => s,
Err(e) => {
eprintln!("skipping cuda_osm_resident_matches_stateless_within_tolerance: no CUDA device ({e})");
return;
}
};
let mut resident = osm_cuda::CudaResident::upload(&peds_initial, &walls)
.expect("OSM resident upload failed after successful CUDA init");
for _ in 0..5 {
state
.step(&mut peds_stateless, &walls, ¶ms, dt)
.expect("CUDA OSM stateless step failed after successful init");
resident
.step(¶ms, dt)
.expect("CUDA OSM resident step failed after successful upload");
}
let mut peds_resident = Vec::new();
resident
.download(&mut peds_resident)
.expect("CUDA OSM resident download failed");
assert_eq!(resident.len(), peds_stateless.len());
assert!(!resident.is_empty());
for (i, (a, b)) in peds_stateless.iter().zip(peds_resident.iter()).enumerate() {
let dx = (a.pos[0] - b.pos[0]).abs();
let dy = (a.pos[1] - b.pos[1]).abs();
let dvx = (a.vel[0] - b.vel[0]).abs();
let dvy = (a.vel[1] - b.vel[1]).abs();
assert!(
dx < 1e-5 && dy < 1e-5,
"agent {i} resident position diverged: stateless={:?} resident={:?}",
a.pos,
b.pos
);
assert!(
dvx < 1e-5 && dvy < 1e-5,
"agent {i} resident velocity diverged: stateless={:?} resident={:?}",
a.vel,
b.vel
);
}
}
#[test]
fn cuda_osm_step_with_fallback_runs_on_cpu_when_no_device() {
let mut peds = seed();
let walls: Vec<WallSegment> = Vec::new();
let params = optimal_steps::Params::default();
let mut state: Option<osm_cuda::CudaState> = None;
let _ok = osm_cuda::step_with_fallback(&mut state, &mut peds, &walls, ¶ms, 0.4);
for p in &peds {
assert!(
p.pos[0].is_finite() && p.pos[1].is_finite(),
"non-finite post-step position: {:?}",
p.pos
);
assert!(
p.vel[0].is_finite() && p.vel[1].is_finite(),
"non-finite post-step velocity: {:?}",
p.vel
);
}
}