rustsim_crowd/
calibration.rs1use crate::common::{Pedestrian, Vec2};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct WeidmannCurve {
13 pub free_speed: f64,
15 pub gamma: f64,
17 pub jam_density: f64,
19}
20
21impl WeidmannCurve {
22 pub const WEIDMANN_1993: Self = Self {
24 free_speed: 1.34,
25 gamma: 1.913,
26 jam_density: 5.40,
27 };
28
29 pub fn speed_at_density(self, rho: f64) -> f64 {
34 if !rho.is_finite() || rho <= 0.0 {
35 return self.free_speed;
36 }
37 if rho >= self.jam_density {
38 return 0.0;
39 }
40 let inner = -self.gamma * (1.0 / rho - 1.0 / self.jam_density);
41 self.free_speed * (1.0 - inner.exp())
42 }
43}
44
45impl Default for WeidmannCurve {
46 fn default() -> Self {
47 Self::WEIDMANN_1993
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq)]
53pub struct CalibrationPoint {
54 pub density: f64,
56 pub measured_speed: f64,
58 pub reference_speed: f64,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq)]
64pub struct CalibrationReport {
65 pub points: usize,
67 pub rms_error: f64,
69 pub max_abs_error: f64,
71}
72
73impl CalibrationReport {
74 pub fn from_points(points: &[CalibrationPoint]) -> Self {
76 let mut count = 0usize;
77 let mut sse = 0.0;
78 let mut max_abs_error = 0.0f64;
79 for point in points {
80 let err = point.measured_speed - point.reference_speed;
81 if !err.is_finite() {
82 continue;
83 }
84 count += 1;
85 sse += err * err;
86 max_abs_error = max_abs_error.max(err.abs());
87 }
88 let rms_error = if count == 0 {
89 f64::INFINITY
90 } else {
91 (sse / count as f64).sqrt()
92 };
93 Self {
94 points: count,
95 rms_error,
96 max_abs_error,
97 }
98 }
99
100 pub fn passes(self, max_abs_tolerance: f64) -> bool {
102 self.points > 0 && self.max_abs_error <= max_abs_tolerance
103 }
104}
105
106pub fn density_for_area(count: usize, area_m2: f64) -> f64 {
108 if count == 0 || area_m2 <= 0.0 || !area_m2.is_finite() {
109 0.0
110 } else {
111 count as f64 / area_m2
112 }
113}
114
115pub fn mean_speed(peds: &[Pedestrian]) -> f64 {
117 if peds.is_empty() {
118 return 0.0;
119 }
120 peds.iter().map(|ped| speed(ped.vel)).sum::<f64>() / peds.len() as f64
121}
122
123pub fn apply_weidmann_speed_cap(peds: &mut [Pedestrian], density: f64, curve: WeidmannCurve) {
130 let cap = curve.speed_at_density(density);
131 for ped in peds {
132 ped.vel = clamp_vec(ped.vel, cap);
133 }
134}
135
136pub fn apply_weidmann_speed_target(peds: &mut [Pedestrian], density: f64, curve: WeidmannCurve) {
142 let target = curve.speed_at_density(density);
143 for ped in peds {
144 let dir = heading_or_destination(*ped);
145 ped.vel = [dir[0] * target, dir[1] * target];
146 }
147}
148
149#[inline]
150fn speed(v: Vec2) -> f64 {
151 (v[0] * v[0] + v[1] * v[1]).sqrt()
152}
153
154#[inline]
155fn clamp_vec(v: Vec2, max_speed: f64) -> Vec2 {
156 let current = speed(v);
157 if current > max_speed && current > 1e-12 {
158 let scale = max_speed / current;
159 [v[0] * scale, v[1] * scale]
160 } else {
161 v
162 }
163}
164
165fn heading_or_destination(ped: Pedestrian) -> Vec2 {
166 let current = speed(ped.vel);
167 if current > 1e-12 {
168 return [ped.vel[0] / current, ped.vel[1] / current];
169 }
170 let to_dest = [
171 ped.destination[0] - ped.pos[0],
172 ped.destination[1] - ped.pos[1],
173 ];
174 let dist = speed(to_dest);
175 if dist > 1e-12 {
176 [to_dest[0] / dist, to_dest[1] / dist]
177 } else {
178 [1.0, 0.0]
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn weidmann_curve_matches_reference_points() {
188 let curve = WeidmannCurve::WEIDMANN_1993;
189 assert!((curve.speed_at_density(0.5) - 1.30).abs() < 0.02);
190 assert!((curve.speed_at_density(1.0) - 1.06).abs() < 0.02);
191 assert!((curve.speed_at_density(2.0) - 0.61).abs() < 0.02);
192 assert_eq!(curve.speed_at_density(curve.jam_density), 0.0);
193 }
194
195 #[test]
196 fn speed_cap_enforces_curve_bound() {
197 let curve = WeidmannCurve::WEIDMANN_1993;
198 let density = 2.0;
199 let mut peds = vec![Pedestrian::new(
200 [0.0, 0.0],
201 [curve.free_speed, 0.0],
202 0.25,
203 curve.free_speed,
204 [10.0, 0.0],
205 )];
206 apply_weidmann_speed_cap(&mut peds, density, curve);
207 assert!(mean_speed(&peds) <= curve.speed_at_density(density) + 1e-12);
208 }
209
210 #[test]
211 fn speed_target_sets_curve_speed() {
212 let curve = WeidmannCurve::WEIDMANN_1993;
213 let density = 2.0;
214 let mut peds = vec![Pedestrian::new(
215 [0.0, 0.0],
216 [0.1, 0.0],
217 0.25,
218 curve.free_speed,
219 [10.0, 0.0],
220 )];
221 apply_weidmann_speed_target(&mut peds, density, curve);
222 assert!((mean_speed(&peds) - curve.speed_at_density(density)).abs() < 1e-12);
223 }
224
225 #[test]
226 fn calibration_report_tracks_error_bounds() {
227 let report = CalibrationReport::from_points(&[
228 CalibrationPoint {
229 density: 0.5,
230 measured_speed: 1.25,
231 reference_speed: 1.30,
232 },
233 CalibrationPoint {
234 density: 1.0,
235 measured_speed: 1.00,
236 reference_speed: 1.05,
237 },
238 ]);
239 assert_eq!(report.points, 2);
240 assert!(report.max_abs_error <= 0.051);
241 assert!(report.passes(0.051));
242 assert!(!report.passes(0.01));
243 }
244}