use rustsim_core::prelude::{Agent, AgentId, AgentStore, SoaExtractableF64};
use crate::broadphase::Scratch;
use crate::common::{Pedestrian, WallSegment};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CrowdAgent {
pub id: AgentId,
pub ped: Pedestrian,
}
impl Agent for CrowdAgent {
#[inline]
fn id(&self) -> AgentId {
self.id
}
}
impl SoaExtractableF64 for CrowdAgent {
fn num_columns() -> usize {
8
}
fn column_names() -> Vec<&'static str> {
vec![
"pos_x",
"pos_y",
"vel_x",
"vel_y",
"radius",
"desired_speed",
"dest_x",
"dest_y",
]
}
fn extract_row(&self, columns: &mut [Vec<f64>]) {
columns[0].push(self.ped.pos[0]);
columns[1].push(self.ped.pos[1]);
columns[2].push(self.ped.vel[0]);
columns[3].push(self.ped.vel[1]);
columns[4].push(self.ped.radius);
columns[5].push(self.ped.desired_speed);
columns[6].push(self.ped.destination[0]);
columns[7].push(self.ped.destination[1]);
}
fn write_back_row(&mut self, columns: &[&[f64]], row: usize) {
self.ped.pos = [columns[0][row], columns[1][row]];
self.ped.vel = [columns[2][row], columns[3][row]];
self.ped.radius = columns[4][row];
self.ped.desired_speed = columns[5][row];
self.ped.destination = [columns[6][row], columns[7][row]];
}
}
pub const NUM_COLUMNS: usize = 8;
pub const COL_POS_X: usize = 0;
pub const COL_POS_Y: usize = 1;
pub const COL_VEL_X: usize = 2;
pub const COL_VEL_Y: usize = 3;
pub const COL_RADIUS: usize = 4;
pub const COL_DESIRED_SPEED: usize = 5;
pub const COL_DEST_X: usize = 6;
pub const COL_DEST_Y: usize = 7;
pub fn unpack_columns_into(columns: &[Vec<f64>], n: usize, peds_buf: &mut Vec<Pedestrian>) {
assert!(
columns.len() >= NUM_COLUMNS,
"CrowdAgent SoA needs {NUM_COLUMNS} columns, got {}",
columns.len()
);
peds_buf.clear();
peds_buf.reserve(n);
let (pos_x, pos_y) = (&columns[COL_POS_X], &columns[COL_POS_Y]);
let (vel_x, vel_y) = (&columns[COL_VEL_X], &columns[COL_VEL_Y]);
let radius = &columns[COL_RADIUS];
let desired_speed = &columns[COL_DESIRED_SPEED];
let (dest_x, dest_y) = (&columns[COL_DEST_X], &columns[COL_DEST_Y]);
for row in 0..n {
peds_buf.push(Pedestrian {
pos: [pos_x[row], pos_y[row]],
vel: [vel_x[row], vel_y[row]],
radius: radius[row],
desired_speed: desired_speed[row],
destination: [dest_x[row], dest_y[row]],
});
}
}
pub fn pack_columns_from(peds: &[Pedestrian], columns: &mut [Vec<f64>]) {
assert!(
columns.len() >= NUM_COLUMNS,
"CrowdAgent SoA needs {NUM_COLUMNS} columns, got {}",
columns.len()
);
for (row, p) in peds.iter().enumerate() {
columns[COL_POS_X][row] = p.pos[0];
columns[COL_POS_Y][row] = p.pos[1];
columns[COL_VEL_X][row] = p.vel[0];
columns[COL_VEL_Y][row] = p.vel[1];
columns[COL_RADIUS][row] = p.radius;
columns[COL_DESIRED_SPEED][row] = p.desired_speed;
columns[COL_DEST_X][row] = p.destination[0];
columns[COL_DEST_Y][row] = p.destination[1];
}
}
#[allow(clippy::too_many_arguments)]
pub fn step_columns_f64<M>(
model: &M,
columns: &mut [Vec<f64>],
n: usize,
walls: &[WallSegment],
params: &M::Params,
dt: f64,
scratch: &mut Scratch,
peds_buf: &mut Vec<Pedestrian>,
) where
M: CrowdStep,
{
unpack_columns_into(columns, n, peds_buf);
model.step(peds_buf, walls, params, dt, scratch);
pack_columns_from(peds_buf, columns);
}
pub trait CrowdStep {
type Params;
fn step(
&self,
peds: &mut [Pedestrian],
walls: &[WallSegment],
params: &Self::Params,
dt: f64,
scratch: &mut Scratch,
);
}
macro_rules! impl_crowd_step {
($model:ident, $module:ident) => {
#[doc = concat!("[`", stringify!($module), "`](crate::", stringify!($module), ") model.")]
#[derive(Debug, Clone, Copy, Default)]
pub struct $model;
impl CrowdStep for $model {
type Params = crate::$module::Params;
#[inline]
fn step(
&self,
peds: &mut [Pedestrian],
walls: &[WallSegment],
params: &Self::Params,
dt: f64,
scratch: &mut Scratch,
) {
crate::$module::step_scratch(peds, walls, params, dt, scratch);
}
}
};
}
impl_crowd_step!(SocialForceModel, social_force);
impl_crowd_step!(CollisionFreeSpeedModel, collision_free_speed);
impl_crowd_step!(
GeneralizedCentrifugalForceModel,
generalized_centrifugal_force
);
impl_crowd_step!(AnticipationVelocityModel, anticipation_velocity);
impl_crowd_step!(OptimalStepsModel, optimal_steps);
#[cfg(feature = "rayon")]
pub trait CrowdStepPar: CrowdStep {
fn step_par(
&self,
peds: &mut [Pedestrian],
walls: &[WallSegment],
params: &Self::Params,
dt: f64,
scratch: &mut Scratch,
);
}
#[cfg(feature = "rayon")]
macro_rules! impl_crowd_step_par {
($model:ident, $module:ident) => {
impl CrowdStepPar for $model {
#[inline]
fn step_par(
&self,
peds: &mut [Pedestrian],
walls: &[WallSegment],
params: &Self::Params,
dt: f64,
scratch: &mut Scratch,
) {
crate::$module::step_scratch_par(peds, walls, params, dt, scratch);
}
}
};
}
#[cfg(feature = "rayon")]
impl_crowd_step_par!(SocialForceModel, social_force);
#[cfg(feature = "rayon")]
impl_crowd_step_par!(CollisionFreeSpeedModel, collision_free_speed);
#[cfg(feature = "rayon")]
impl_crowd_step_par!(
GeneralizedCentrifugalForceModel,
generalized_centrifugal_force
);
#[cfg(feature = "rayon")]
impl_crowd_step_par!(AnticipationVelocityModel, anticipation_velocity);
#[cfg(feature = "rayon")]
impl_crowd_step_par!(OptimalStepsModel, optimal_steps);
pub trait CrowdObserver {
fn observe(&mut self, agent_id: AgentId, ped: &Pedestrian);
}
impl<F> CrowdObserver for F
where
F: FnMut(AgentId, &Pedestrian),
{
#[inline]
fn observe(&mut self, agent_id: AgentId, ped: &Pedestrian) {
self(agent_id, ped);
}
}
#[derive(Debug, Clone, Copy, Default)]
struct NoopObserver;
impl CrowdObserver for NoopObserver {
#[inline]
fn observe(&mut self, _agent_id: AgentId, _ped: &Pedestrian) {}
}
pub fn step_scratch_store<M, S>(
model: &M,
store: &mut S,
walls: &[WallSegment],
params: &M::Params,
dt: f64,
scratch: &mut Scratch,
peds_buf: &mut Vec<Pedestrian>,
) where
M: CrowdStep,
S: AgentStore<CrowdAgent>,
{
step_scratch_store_observed(
model,
store,
walls,
params,
dt,
scratch,
peds_buf,
&mut NoopObserver,
);
}
#[allow(clippy::too_many_arguments)]
pub fn step_scratch_store_observed<M, S, O>(
model: &M,
store: &mut S,
walls: &[WallSegment],
params: &M::Params,
dt: f64,
scratch: &mut Scratch,
peds_buf: &mut Vec<Pedestrian>,
observer: &mut O,
) where
M: CrowdStep,
S: AgentStore<CrowdAgent>,
O: CrowdObserver + ?Sized,
{
let ids = store.iter_ids();
peds_buf.clear();
peds_buf.reserve(ids.len());
for &id in &ids {
if let Some(agent) = store.get(id) {
peds_buf.push(agent.ped);
}
}
model.step(peds_buf, walls, params, dt, scratch);
for (row, &id) in ids.iter().enumerate() {
if let Some(mut agent) = store.get_mut(id) {
agent.ped = peds_buf[row];
}
}
for (row, &id) in ids.iter().enumerate() {
observer.observe(id, &peds_buf[row]);
}
}
#[cfg(feature = "rayon")]
#[allow(clippy::too_many_arguments)]
pub fn step_scratch_store_par<M, S>(
model: &M,
store: &mut S,
walls: &[WallSegment],
params: &M::Params,
dt: f64,
scratch: &mut Scratch,
peds_buf: &mut Vec<Pedestrian>,
) where
M: CrowdStepPar,
S: AgentStore<CrowdAgent>,
{
step_scratch_store_observed_par(
model,
store,
walls,
params,
dt,
scratch,
peds_buf,
&mut NoopObserver,
);
}
#[cfg(feature = "rayon")]
#[allow(clippy::too_many_arguments)]
pub fn step_scratch_store_observed_par<M, S, O>(
model: &M,
store: &mut S,
walls: &[WallSegment],
params: &M::Params,
dt: f64,
scratch: &mut Scratch,
peds_buf: &mut Vec<Pedestrian>,
observer: &mut O,
) where
M: CrowdStepPar,
S: AgentStore<CrowdAgent>,
O: CrowdObserver + ?Sized,
{
let ids = store.iter_ids();
peds_buf.clear();
peds_buf.reserve(ids.len());
for &id in &ids {
if let Some(agent) = store.get(id) {
peds_buf.push(agent.ped);
}
}
model.step_par(peds_buf, walls, params, dt, scratch);
for (row, &id) in ids.iter().enumerate() {
if let Some(mut agent) = store.get_mut(id) {
agent.ped = peds_buf[row];
}
}
for (row, &id) in ids.iter().enumerate() {
observer.observe(id, &peds_buf[row]);
}
}
#[cfg(test)]
mod tests {
use super::*;
use rustsim_core::prelude::VecStore;
fn agent_at(id: AgentId, x: f64, dest: f64) -> CrowdAgent {
CrowdAgent {
id,
ped: Pedestrian {
pos: [x, 0.0],
vel: [0.0, 0.0],
radius: 0.25,
desired_speed: 1.34,
destination: [dest, 0.0],
},
}
}
#[test]
fn soa_round_trip_is_identity() {
let a = agent_at(7, 1.5, 9.0);
let mut cols: Vec<Vec<f64>> = (0..CrowdAgent::num_columns()).map(|_| Vec::new()).collect();
a.extract_row(&mut cols);
let col_refs: Vec<&[f64]> = cols.iter().map(|c| c.as_slice()).collect();
let mut b = agent_at(7, 0.0, 0.0);
b.write_back_row(&col_refs, 0);
assert_eq!(a, b);
}
#[test]
fn step_scratch_store_advances_toward_destination() {
let mut store: VecStore<CrowdAgent> = VecStore::new();
store.insert(agent_at(0, 0.0, 10.0));
let params = crate::social_force::Params::default();
let mut scratch = Scratch::with_capacity(1, crate::social_force::neighbor_cutoff(¶ms));
let mut buf: Vec<Pedestrian> = Vec::with_capacity(1);
for _ in 0..100 {
step_scratch_store(
&SocialForceModel,
&mut store,
&[],
¶ms,
0.05,
&mut scratch,
&mut buf,
);
}
let pos_x = store.get(0).unwrap().ped.pos[0];
assert!(pos_x > 1.0, "agent should have advanced: pos_x={pos_x}");
}
#[test]
fn step_scratch_store_matches_direct_step_scratch() {
let make = || -> Vec<CrowdAgent> {
(0..8)
.map(|k| {
let x = (k as f64) * 1.2;
agent_at(k as AgentId, x, x + 5.0)
})
.collect()
};
let a = make();
let b = make();
let mut peds_a: Vec<Pedestrian> = a.iter().map(|c| c.ped).collect();
let params = crate::social_force::Params::default();
let cutoff = crate::social_force::neighbor_cutoff(¶ms);
let mut scratch_a = Scratch::with_capacity(peds_a.len(), cutoff);
for _ in 0..30 {
crate::social_force::step_scratch(&mut peds_a, &[], ¶ms, 0.05, &mut scratch_a);
}
let mut store: VecStore<CrowdAgent> = VecStore::new();
for agent in b {
store.insert(agent);
}
let mut scratch_b = Scratch::with_capacity(8, cutoff);
let mut buf: Vec<Pedestrian> = Vec::with_capacity(8);
for _ in 0..30 {
step_scratch_store(
&SocialForceModel,
&mut store,
&[],
¶ms,
0.05,
&mut scratch_b,
&mut buf,
);
}
for (i, direct) in peds_a.iter().enumerate() {
let via_store = store.get(i as AgentId).unwrap().ped;
assert_eq!(direct.pos, via_store.pos, "position diverged at agent {i}");
assert_eq!(direct.vel, via_store.vel, "velocity diverged at agent {i}");
}
}
#[test]
fn step_columns_f64_matches_direct_step_scratch() {
let make = || -> Vec<Pedestrian> {
(0..8)
.map(|k| {
let x = (k as f64) * 1.2;
Pedestrian {
pos: [x, 0.0],
vel: [0.0, 0.0],
radius: 0.25,
desired_speed: 1.34,
destination: [x + 5.0, 0.0],
}
})
.collect()
};
let mut peds_aos = make();
let peds_soa = make();
let params = crate::social_force::Params::default();
let cutoff = crate::social_force::neighbor_cutoff(¶ms);
let mut scratch_a = Scratch::with_capacity(peds_aos.len(), cutoff);
for _ in 0..30 {
crate::social_force::step_scratch(&mut peds_aos, &[], ¶ms, 0.05, &mut scratch_a);
}
let n = peds_soa.len();
let mut columns: Vec<Vec<f64>> = (0..NUM_COLUMNS).map(|_| Vec::with_capacity(n)).collect();
for p in &peds_soa {
columns[COL_POS_X].push(p.pos[0]);
columns[COL_POS_Y].push(p.pos[1]);
columns[COL_VEL_X].push(p.vel[0]);
columns[COL_VEL_Y].push(p.vel[1]);
columns[COL_RADIUS].push(p.radius);
columns[COL_DESIRED_SPEED].push(p.desired_speed);
columns[COL_DEST_X].push(p.destination[0]);
columns[COL_DEST_Y].push(p.destination[1]);
}
let mut scratch_b = Scratch::with_capacity(n, cutoff);
let mut peds_buf: Vec<Pedestrian> = Vec::with_capacity(n);
for _ in 0..30 {
step_columns_f64(
&SocialForceModel,
&mut columns,
n,
&[],
¶ms,
0.05,
&mut scratch_b,
&mut peds_buf,
);
}
for i in 0..n {
assert_eq!(
peds_aos[i].pos,
[columns[COL_POS_X][i], columns[COL_POS_Y][i]],
"position diverged at agent {i}"
);
assert_eq!(
peds_aos[i].vel,
[columns[COL_VEL_X][i], columns[COL_VEL_Y][i]],
"velocity diverged at agent {i}"
);
}
}
#[test]
fn observer_sees_post_tick_state_in_id_order() {
let mut store: VecStore<CrowdAgent> = VecStore::new();
for k in 0..4 {
store.insert(agent_at(
k as AgentId,
(k as f64) * 1.5,
(k as f64) * 1.5 + 5.0,
));
}
let params = crate::social_force::Params::default();
let mut scratch = Scratch::with_capacity(4, crate::social_force::neighbor_cutoff(¶ms));
let mut buf: Vec<Pedestrian> = Vec::with_capacity(4);
let mut seen: Vec<(AgentId, [f64; 2])> = Vec::new();
step_scratch_store_observed(
&SocialForceModel,
&mut store,
&[],
¶ms,
0.05,
&mut scratch,
&mut buf,
&mut |id: AgentId, ped: &Pedestrian| {
seen.push((id, ped.pos));
},
);
assert_eq!(seen.len(), 4);
for (row, (id, pos)) in seen.iter().enumerate() {
assert_eq!(*id, row as AgentId);
let stored = store.get(*id).unwrap().ped.pos;
assert_eq!(*pos, stored, "observer saw stale pos at agent {id}");
}
}
#[test]
fn observed_step_matches_unobserved_step() {
let make = || -> Vec<CrowdAgent> {
(0..6)
.map(|k| agent_at(k as AgentId, (k as f64) * 1.0, (k as f64) * 1.0 + 5.0))
.collect()
};
let params = crate::social_force::Params::default();
let cutoff = crate::social_force::neighbor_cutoff(¶ms);
let mut store_a: VecStore<CrowdAgent> = VecStore::new();
for a in make() {
store_a.insert(a);
}
let mut scratch_a = Scratch::with_capacity(6, cutoff);
let mut buf_a: Vec<Pedestrian> = Vec::with_capacity(6);
let mut store_b: VecStore<CrowdAgent> = VecStore::new();
for a in make() {
store_b.insert(a);
}
let mut scratch_b = Scratch::with_capacity(6, cutoff);
let mut buf_b: Vec<Pedestrian> = Vec::with_capacity(6);
for _ in 0..20 {
step_scratch_store(
&SocialForceModel,
&mut store_a,
&[],
¶ms,
0.05,
&mut scratch_a,
&mut buf_a,
);
step_scratch_store_observed(
&SocialForceModel,
&mut store_b,
&[],
¶ms,
0.05,
&mut scratch_b,
&mut buf_b,
&mut |_id: AgentId, _p: &Pedestrian| {},
);
}
for id in 0..6u64 {
let a = store_a.get(id).unwrap().ped;
let b = store_b.get(id).unwrap().ped;
assert_eq!(a.pos, b.pos, "pos diverged at agent {id}");
assert_eq!(a.vel, b.vel, "vel diverged at agent {id}");
}
}
}