use crate::common::{Pedestrian, Vec2};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WeidmannCurve {
pub free_speed: f64,
pub gamma: f64,
pub jam_density: f64,
}
impl WeidmannCurve {
pub const WEIDMANN_1993: Self = Self {
free_speed: 1.34,
gamma: 1.913,
jam_density: 5.40,
};
pub fn speed_at_density(self, rho: f64) -> f64 {
if !rho.is_finite() || rho <= 0.0 {
return self.free_speed;
}
if rho >= self.jam_density {
return 0.0;
}
let inner = -self.gamma * (1.0 / rho - 1.0 / self.jam_density);
self.free_speed * (1.0 - inner.exp())
}
}
impl Default for WeidmannCurve {
fn default() -> Self {
Self::WEIDMANN_1993
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CalibrationPoint {
pub density: f64,
pub measured_speed: f64,
pub reference_speed: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CalibrationReport {
pub points: usize,
pub rms_error: f64,
pub max_abs_error: f64,
}
impl CalibrationReport {
pub fn from_points(points: &[CalibrationPoint]) -> Self {
let mut count = 0usize;
let mut sse = 0.0;
let mut max_abs_error = 0.0f64;
for point in points {
let err = point.measured_speed - point.reference_speed;
if !err.is_finite() {
continue;
}
count += 1;
sse += err * err;
max_abs_error = max_abs_error.max(err.abs());
}
let rms_error = if count == 0 {
f64::INFINITY
} else {
(sse / count as f64).sqrt()
};
Self {
points: count,
rms_error,
max_abs_error,
}
}
pub fn passes(self, max_abs_tolerance: f64) -> bool {
self.points > 0 && self.max_abs_error <= max_abs_tolerance
}
}
pub fn density_for_area(count: usize, area_m2: f64) -> f64 {
if count == 0 || area_m2 <= 0.0 || !area_m2.is_finite() {
0.0
} else {
count as f64 / area_m2
}
}
pub fn mean_speed(peds: &[Pedestrian]) -> f64 {
if peds.is_empty() {
return 0.0;
}
peds.iter().map(|ped| speed(ped.vel)).sum::<f64>() / peds.len() as f64
}
pub fn apply_weidmann_speed_cap(peds: &mut [Pedestrian], density: f64, curve: WeidmannCurve) {
let cap = curve.speed_at_density(density);
for ped in peds {
ped.vel = clamp_vec(ped.vel, cap);
}
}
pub fn apply_weidmann_speed_target(peds: &mut [Pedestrian], density: f64, curve: WeidmannCurve) {
let target = curve.speed_at_density(density);
for ped in peds {
let dir = heading_or_destination(*ped);
ped.vel = [dir[0] * target, dir[1] * target];
}
}
#[inline]
fn speed(v: Vec2) -> f64 {
(v[0] * v[0] + v[1] * v[1]).sqrt()
}
#[inline]
fn clamp_vec(v: Vec2, max_speed: f64) -> Vec2 {
let current = speed(v);
if current > max_speed && current > 1e-12 {
let scale = max_speed / current;
[v[0] * scale, v[1] * scale]
} else {
v
}
}
fn heading_or_destination(ped: Pedestrian) -> Vec2 {
let current = speed(ped.vel);
if current > 1e-12 {
return [ped.vel[0] / current, ped.vel[1] / current];
}
let to_dest = [
ped.destination[0] - ped.pos[0],
ped.destination[1] - ped.pos[1],
];
let dist = speed(to_dest);
if dist > 1e-12 {
[to_dest[0] / dist, to_dest[1] / dist]
} else {
[1.0, 0.0]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn weidmann_curve_matches_reference_points() {
let curve = WeidmannCurve::WEIDMANN_1993;
assert!((curve.speed_at_density(0.5) - 1.30).abs() < 0.02);
assert!((curve.speed_at_density(1.0) - 1.06).abs() < 0.02);
assert!((curve.speed_at_density(2.0) - 0.61).abs() < 0.02);
assert_eq!(curve.speed_at_density(curve.jam_density), 0.0);
}
#[test]
fn speed_cap_enforces_curve_bound() {
let curve = WeidmannCurve::WEIDMANN_1993;
let density = 2.0;
let mut peds = vec![Pedestrian::new(
[0.0, 0.0],
[curve.free_speed, 0.0],
0.25,
curve.free_speed,
[10.0, 0.0],
)];
apply_weidmann_speed_cap(&mut peds, density, curve);
assert!(mean_speed(&peds) <= curve.speed_at_density(density) + 1e-12);
}
#[test]
fn speed_target_sets_curve_speed() {
let curve = WeidmannCurve::WEIDMANN_1993;
let density = 2.0;
let mut peds = vec![Pedestrian::new(
[0.0, 0.0],
[0.1, 0.0],
0.25,
curve.free_speed,
[10.0, 0.0],
)];
apply_weidmann_speed_target(&mut peds, density, curve);
assert!((mean_speed(&peds) - curve.speed_at_density(density)).abs() < 1e-12);
}
#[test]
fn calibration_report_tracks_error_bounds() {
let report = CalibrationReport::from_points(&[
CalibrationPoint {
density: 0.5,
measured_speed: 1.25,
reference_speed: 1.30,
},
CalibrationPoint {
density: 1.0,
measured_speed: 1.00,
reference_speed: 1.05,
},
]);
assert_eq!(report.points, 2);
assert!(report.max_abs_error <= 0.051);
assert!(report.passes(0.051));
assert!(!report.passes(0.01));
}
}