pub const WEIDMANN_GAMMA: f64 = 1.913;
pub const WEIDMANN_RHO_JAM: f64 = 5.4;
pub const WEIDMANN_FREE_FLOW_SPEED: f64 = 1.34;
pub fn weidmann_speed(v0: f64, density_ped_per_m2: f64) -> f64 {
let rho = density_ped_per_m2.max(0.0);
if rho < 1e-6 {
return v0;
}
if rho >= WEIDMANN_RHO_JAM {
return 0.0;
}
let factor = 1.0 - (-WEIDMANN_GAMMA * (1.0 / rho - 1.0 / WEIDMANN_RHO_JAM)).exp();
v0 * factor.clamp(0.0, 1.0)
}
pub fn weidmann_flow(v0: f64, density_ped_per_m2: f64) -> f64 {
density_ped_per_m2.max(0.0) * weidmann_speed(v0, density_ped_per_m2)
}
pub fn weidmann_density_factor(density_ped_per_m2: f64) -> f64 {
let rho = density_ped_per_m2.max(0.0);
if rho < 1e-6 {
return 1.0;
}
if rho >= WEIDMANN_RHO_JAM {
return 0.0;
}
let factor = 1.0 - (-WEIDMANN_GAMMA * (1.0 / rho - 1.0 / WEIDMANN_RHO_JAM)).exp();
factor.clamp(0.0, 1.0)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PedestrianLinkProperties {
pub length_m: f64,
pub width_m: f64,
pub free_flow_speed_mps: f64,
pub speed_limit_mps: f64,
pub capacity: u32,
pub link_class: PedestrianLinkClass,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PedestrianLinkClass {
Corridor,
WalkableFloor,
Stairway,
Ramp,
Escalator,
Crossing,
}
impl PedestrianLinkProperties {
pub fn corridor(length_m: f64, width_m: f64) -> Self {
Self {
length_m,
width_m,
free_flow_speed_mps: WEIDMANN_FREE_FLOW_SPEED,
speed_limit_mps: 2.0,
capacity: 0,
link_class: PedestrianLinkClass::Corridor,
}
}
pub fn floor(length_m: f64, width_m: f64) -> Self {
Self {
length_m,
width_m,
free_flow_speed_mps: WEIDMANN_FREE_FLOW_SPEED,
speed_limit_mps: 2.0,
capacity: 0,
link_class: PedestrianLinkClass::WalkableFloor,
}
}
pub fn stairway(length_m: f64, width_m: f64) -> Self {
Self {
length_m,
width_m,
free_flow_speed_mps: 0.6,
speed_limit_mps: 1.0,
capacity: 0,
link_class: PedestrianLinkClass::Stairway,
}
}
pub fn with_free_flow_speed(mut self, speed_mps: f64) -> Self {
self.free_flow_speed_mps = speed_mps;
self
}
pub fn with_speed_limit(mut self, speed_mps: f64) -> Self {
self.speed_limit_mps = speed_mps;
self
}
pub fn with_capacity(mut self, capacity: u32) -> Self {
self.capacity = capacity;
self
}
pub fn speed_for_count(&self, count: usize) -> f64 {
let area = self.length_m * self.width_m;
if area <= 0.0 {
return self.free_flow_speed_mps;
}
let density = count as f64 / area;
weidmann_speed(self.free_flow_speed_mps, density).min(self.speed_limit_mps)
}
pub fn free_flow_time_s(&self) -> f64 {
if self.free_flow_speed_mps <= 0.0 {
return f64::INFINITY;
}
self.length_m / self.free_flow_speed_mps
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn weidmann_free_flow_at_zero_density() {
let v = weidmann_speed(1.34, 0.0);
assert!((v - 1.34).abs() < 1e-6);
}
#[test]
fn weidmann_speed_decreases_with_density() {
let v0 = 1.34;
let v_low = weidmann_speed(v0, 0.5);
let v_mid = weidmann_speed(v0, 2.0);
let v_high = weidmann_speed(v0, 4.0);
assert!(v_low > v_mid);
assert!(v_mid > v_high);
assert!(v_high > 0.0);
}
#[test]
fn weidmann_zero_at_jam_density() {
let v = weidmann_speed(1.34, WEIDMANN_RHO_JAM);
assert!((v - 0.0).abs() < 1e-6);
}
#[test]
fn weidmann_speed_never_negative() {
for d in [0.0, 0.1, 1.0, 3.0, 5.0, 5.4, 10.0, 100.0] {
assert!(weidmann_speed(1.34, d) >= 0.0);
}
}
#[test]
fn weidmann_speed_never_exceeds_free_flow() {
for d in [0.0, 0.01, 0.1, 0.5, 1.0, 2.0, 5.0] {
assert!(weidmann_speed(1.34, d) <= 1.34 + 1e-12);
}
}
#[test]
fn weidmann_flow_peaks_at_intermediate_density() {
let v0 = 1.34;
let flow_low = weidmann_flow(v0, 0.1);
let flow_mid = weidmann_flow(v0, 1.5);
let flow_high = weidmann_flow(v0, 5.0);
let flow_zero = weidmann_flow(v0, 0.0);
assert_eq!(flow_zero, 0.0);
assert!(flow_mid > flow_low);
assert!(flow_mid > flow_high);
}
#[test]
fn density_factor_range() {
assert!((weidmann_density_factor(0.0) - 1.0).abs() < 1e-6);
assert!((weidmann_density_factor(WEIDMANN_RHO_JAM) - 0.0).abs() < 1e-6);
let mid = weidmann_density_factor(2.0);
assert!(mid > 0.0 && mid < 1.0);
}
#[test]
fn pedestrian_corridor_defaults() {
let link = PedestrianLinkProperties::corridor(50.0, 3.0);
assert!((link.length_m - 50.0).abs() < 1e-6);
assert!((link.width_m - 3.0).abs() < 1e-6);
assert!((link.free_flow_speed_mps - WEIDMANN_FREE_FLOW_SPEED).abs() < 1e-6);
assert_eq!(link.capacity, 0);
assert_eq!(link.link_class, PedestrianLinkClass::Corridor);
}
#[test]
fn stairway_slower_than_corridor() {
let corridor = PedestrianLinkProperties::corridor(50.0, 2.0);
let stairway = PedestrianLinkProperties::stairway(50.0, 2.0);
assert!(stairway.free_flow_speed_mps < corridor.free_flow_speed_mps);
}
#[test]
fn speed_for_count_uses_weidmann() {
let link = PedestrianLinkProperties::corridor(100.0, 2.0);
let speed_empty = link.speed_for_count(0);
let speed_crowded = link.speed_for_count(500);
assert!((speed_empty - WEIDMANN_FREE_FLOW_SPEED).abs() < 1e-6);
assert!(speed_crowded < speed_empty);
}
#[test]
fn free_flow_time_computed() {
let link = PedestrianLinkProperties::corridor(100.0, 2.0);
let expected = 100.0 / WEIDMANN_FREE_FLOW_SPEED;
assert!((link.free_flow_time_s() - expected).abs() < 1e-6);
}
}