1use crate::policy::{FifoGapPolicy, QueuePolicy, SpeedDecision};
17use rustsim_core::types::{EdgeId, LevelRelation, NodeId, SemanticEntity, ZoneId};
18use rustsim_modes::AllowedModes;
19use rustsim_spaces::link::{LinkGeometry, LinkGeometryError, LinkId, LinkSpace, LinkSpaceError};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23pub enum TrafficControlType {
24 #[default]
25 Uncontrolled,
26 Yield,
27 Stop,
28 Signal,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum TurnType {
34 Left,
35 Through,
36 Right,
37 UTurn,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
42pub enum LinkClass {
43 #[default]
44 Road,
45 Walkway,
46 Cycleway,
47 Transitway,
48 Rail,
49 Shared,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq)]
54pub enum FundamentalDiagram {
55 Greenshields,
56 Underwood {
57 k_opt: Option<f64>,
58 },
59 Triangular,
60 FreeFlow,
61 Weidmann,
70}
71
72impl FundamentalDiagram {
73 pub fn speed(&self, density_per_km: f64, free_flow_speed: f64, jam_density: f64) -> f64 {
75 if density_per_km <= 0.0 || jam_density <= 0.0 {
76 return free_flow_speed;
77 }
78
79 let speed = match self {
80 FundamentalDiagram::Greenshields => {
81 free_flow_speed * (1.0 - density_per_km / jam_density).max(0.0)
82 }
83 FundamentalDiagram::Underwood { k_opt } => {
84 let k_opt_val = k_opt.unwrap_or(jam_density / std::f64::consts::E);
85 if k_opt_val <= 0.0 {
86 free_flow_speed
87 } else {
88 free_flow_speed * (-density_per_km / k_opt_val).exp()
89 }
90 }
91 FundamentalDiagram::Triangular => {
92 let k_c = jam_density / 2.0;
93 if density_per_km <= k_c {
94 free_flow_speed
95 } else {
96 free_flow_speed * (jam_density - density_per_km) / (jam_density - k_c)
97 }
98 .max(0.0)
99 }
100 FundamentalDiagram::FreeFlow => free_flow_speed,
101 FundamentalDiagram::Weidmann => {
102 crate::pedestrian_links::weidmann_speed(free_flow_speed, density_per_km)
103 }
104 };
105
106 speed.max(0.0)
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq)]
121pub struct LinkProperties {
122 pub free_flow_speed: f64,
124 pub capacity: f64,
126 pub lanes: u32,
128 pub jam_density: f64,
130 pub diagram: FundamentalDiagram,
132}
133
134impl LinkProperties {
135 pub fn urban(
147 length: f64,
148 speed_kmh: f64,
149 lanes: u32,
150 ) -> Result<(LinkGeometry, Self), LinkGeometryError> {
151 Ok((
152 LinkGeometry::new(length)?,
153 Self {
154 free_flow_speed: speed_kmh / 3.6,
155 capacity: 900.0 * lanes as f64,
156 lanes,
157 jam_density: 150.0,
158 diagram: FundamentalDiagram::Greenshields,
159 },
160 ))
161 }
162
163 pub fn freeway(
175 length: f64,
176 speed_kmh: f64,
177 lanes: u32,
178 ) -> Result<(LinkGeometry, Self), LinkGeometryError> {
179 Ok((
180 LinkGeometry::new(length)?,
181 Self {
182 free_flow_speed: speed_kmh / 3.6,
183 capacity: 2200.0 * lanes as f64,
184 lanes,
185 jam_density: 120.0,
186 diagram: FundamentalDiagram::Underwood { k_opt: None },
187 },
188 ))
189 }
190
191 pub fn pedestrian(length: f64, width: f64) -> Result<(LinkGeometry, Self), LinkGeometryError> {
203 Ok((
204 LinkGeometry::new(length)?,
205 Self {
206 free_flow_speed: 1.3,
207 capacity: 4800.0 * width,
208 lanes: (width.ceil() as u32).max(1),
209 jam_density: 5000.0,
210 diagram: FundamentalDiagram::Greenshields,
211 },
212 ))
213 }
214}
215
216#[derive(Debug, Clone, Copy, PartialEq)]
218pub struct TransportLinkMetadata {
219 pub edge_id: EdgeId,
221 pub from_node: NodeId,
223 pub to_node: NodeId,
225 pub link_class: LinkClass,
227 pub allowed_modes: AllowedModes,
229 pub traffic_control: Option<TrafficControlType>,
231 pub turn_type: Option<TurnType>,
233 pub speed_limit_kph: Option<f64>,
235}
236
237impl TransportLinkMetadata {
238 pub fn new(
240 edge_id: EdgeId,
241 from_node: NodeId,
242 to_node: NodeId,
243 link_class: LinkClass,
244 allowed_modes: AllowedModes,
245 ) -> Self {
246 Self {
247 edge_id,
248 from_node,
249 to_node,
250 link_class,
251 allowed_modes,
252 traffic_control: None,
253 turn_type: None,
254 speed_limit_kph: None,
255 }
256 }
257
258 pub fn with_traffic_control(mut self, traffic_control: TrafficControlType) -> Self {
260 self.traffic_control = Some(traffic_control);
261 self
262 }
263
264 pub fn with_turn_type(mut self, turn_type: TurnType) -> Self {
266 self.turn_type = Some(turn_type);
267 self
268 }
269
270 pub fn with_speed_limit_kph(mut self, speed_limit_kph: f64) -> Self {
272 self.speed_limit_kph = Some(speed_limit_kph);
273 self
274 }
275}
276
277impl SemanticEntity for TransportLinkMetadata {
278 type Id = EdgeId;
279
280 fn semantic_id(&self) -> Self::Id {
281 self.edge_id
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq)]
287pub struct TransitStopMetadata {
288 pub node_id: NodeId,
290 pub zone_id: Option<ZoneId>,
292 pub level_relation: Option<LevelRelation>,
294 pub served_modes: AllowedModes,
296}
297
298impl TransitStopMetadata {
299 pub fn new(node_id: NodeId, served_modes: AllowedModes) -> Self {
301 Self {
302 node_id,
303 zone_id: None,
304 level_relation: None,
305 served_modes,
306 }
307 }
308
309 pub fn with_zone(mut self, zone_id: ZoneId) -> Self {
311 self.zone_id = Some(zone_id);
312 self
313 }
314
315 pub fn with_level_relation(mut self, level_relation: LevelRelation) -> Self {
317 self.level_relation = Some(level_relation);
318 self
319 }
320}
321
322impl SemanticEntity for TransitStopMetadata {
323 type Id = NodeId;
324
325 fn semantic_id(&self) -> Self::Id {
326 self.node_id
327 }
328}
329
330pub type TransportLinkSpace = LinkSpace<LinkProperties>;
332
333pub trait TransportLinkOps {
339 fn link_density(&self, link_id: LinkId) -> f64;
341 fn link_speed(&self, link_id: LinkId) -> f64;
343 fn link_free_flow_time(&self, link_id: LinkId) -> f64;
345 fn link_travel_time(&self, link_id: LinkId) -> f64;
347 fn agent_speed(&self, id: u64) -> Result<f64, LinkSpaceError>;
349 fn agent_speed_decision<P: QueuePolicy>(
351 &self,
352 id: u64,
353 policy: &P,
354 ) -> Result<SpeedDecision, LinkSpaceError>;
355 fn agent_speed_with_policy<P: QueuePolicy>(
357 &self,
358 id: u64,
359 policy: &P,
360 ) -> Result<f64, LinkSpaceError>;
361 fn volume_capacity_ratio(&self, link_id: LinkId, time_window_s: f64) -> f64;
363}
364
365impl TransportLinkOps for LinkSpace<LinkProperties> {
366 fn link_density(&self, link_id: LinkId) -> f64 {
367 let Some(props) = self.link_properties(link_id) else {
368 return 0.0;
369 };
370 let Some(length) = self.link_length(link_id) else {
371 return 0.0;
372 };
373 let n = self.agents_on_link(link_id) as f64;
374 let length_km = length / 1000.0;
375 let lanes = props.lanes.max(1) as f64;
376 if length_km <= 0.0 {
377 return 0.0;
378 }
379 n / (length_km * lanes)
380 }
381
382 fn link_speed(&self, link_id: LinkId) -> f64 {
383 let Some(props) = self.link_properties(link_id) else {
384 return 0.0;
385 };
386 props.diagram.speed(
387 self.link_density(link_id),
388 props.free_flow_speed,
389 props.jam_density,
390 )
391 }
392
393 fn link_free_flow_time(&self, link_id: LinkId) -> f64 {
394 let Some(props) = self.link_properties(link_id) else {
395 return f64::INFINITY;
396 };
397 let Some(length) = self.link_length(link_id) else {
398 return f64::INFINITY;
399 };
400 length / props.free_flow_speed
401 }
402
403 fn link_travel_time(&self, link_id: LinkId) -> f64 {
404 let speed = self.link_speed(link_id);
405 if speed <= 0.0 {
406 return f64::INFINITY;
407 }
408 let Some(length) = self.link_length(link_id) else {
409 return f64::INFINITY;
410 };
411 length / speed
412 }
413
414 fn agent_speed(&self, id: u64) -> Result<f64, LinkSpaceError> {
415 self.agent_speed_with_policy(id, &FifoGapPolicy::default())
416 }
417
418 fn agent_speed_decision<P: QueuePolicy>(
419 &self,
420 id: u64,
421 policy: &P,
422 ) -> Result<SpeedDecision, LinkSpaceError> {
423 policy.speed_for(self, id)
424 }
425
426 fn agent_speed_with_policy<P: QueuePolicy>(
427 &self,
428 id: u64,
429 policy: &P,
430 ) -> Result<f64, LinkSpaceError> {
431 Ok(self.agent_speed_decision(id, policy)?.speed)
432 }
433
434 fn volume_capacity_ratio(&self, link_id: LinkId, time_window_s: f64) -> f64 {
435 let Some(props) = self.link_properties(link_id) else {
436 return 0.0;
437 };
438 let count = self.agents_on_link(link_id) as f64;
439 let flow_rate = count / (time_window_s / 3600.0);
440 flow_rate / props.capacity
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447 use rustsim_modes::TravelMode;
448
449 #[test]
450 fn transport_link_metadata_builder_helpers() {
451 let link = TransportLinkMetadata::new(7, 1, 2, LinkClass::Road, AllowedModes::vehicular())
452 .with_traffic_control(TrafficControlType::Signal)
453 .with_turn_type(TurnType::Through)
454 .with_speed_limit_kph(50.0);
455
456 assert_eq!(link.semantic_id(), 7);
457 assert_eq!(link.from_node, 1);
458 assert_eq!(link.to_node, 2);
459 assert_eq!(link.link_class, LinkClass::Road);
460 assert!(link.allowed_modes.allows(TravelMode::Vehicle));
461 assert!(link.allowed_modes.allows(TravelMode::Transit));
462 assert_eq!(link.traffic_control, Some(TrafficControlType::Signal));
463 assert_eq!(link.turn_type, Some(TurnType::Through));
464 assert_eq!(link.speed_limit_kph, Some(50.0));
465 }
466
467 #[test]
468 fn transit_stop_metadata_builder_helpers() {
469 let stop = TransitStopMetadata::new(3, AllowedModes::none().with_mode(TravelMode::Transit))
470 .with_zone(10)
471 .with_level_relation(LevelRelation::on(2));
472
473 assert_eq!(stop.semantic_id(), 3);
474 assert_eq!(stop.zone_id, Some(10));
475 assert_eq!(stop.level_relation, Some(LevelRelation::on(2)));
476 assert!(stop.served_modes.allows(TravelMode::Transit));
477 }
478
479 #[test]
480 fn fundamental_diagram_and_link_ops_work() {
481 let fd = FundamentalDiagram::Greenshields;
482 assert!((fd.speed(75.0, 13.9, 150.0) - 6.95).abs() < 0.1);
483
484 let mut space: TransportLinkSpace = LinkSpace::new();
485 let a = space.add_node();
486 let b = space.add_node();
487 let (geom, props) = LinkProperties::urban(500.0, 50.0, 2).unwrap();
488 let link = space.add_link(a, b, geom, props).unwrap();
489 for i in 1..=15 {
490 space.add_agent_to_link(i, link, (i as f64) * 30.0).unwrap();
491 }
492
493 assert!((space.link_density(link) - 15.0).abs() < 1e-6);
494 assert!((space.link_speed(link) - 12.51).abs() < 0.1);
495 let ff_time = space.link_free_flow_time(link);
496 assert!((ff_time - 36.0).abs() < 1.0);
497 assert!(space.volume_capacity_ratio(link, 3600.0) > 0.0);
498 }
499}