use crate::policy::{FifoGapPolicy, QueuePolicy, SpeedDecision};
use rustsim_core::types::{EdgeId, LevelRelation, NodeId, SemanticEntity, ZoneId};
use rustsim_modes::AllowedModes;
use rustsim_spaces::link::{LinkGeometry, LinkGeometryError, LinkId, LinkSpace, LinkSpaceError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TrafficControlType {
#[default]
Uncontrolled,
Yield,
Stop,
Signal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TurnType {
Left,
Through,
Right,
UTurn,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum LinkClass {
#[default]
Road,
Walkway,
Cycleway,
Transitway,
Rail,
Shared,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FundamentalDiagram {
Greenshields,
Underwood {
k_opt: Option<f64>,
},
Triangular,
FreeFlow,
Weidmann,
}
impl FundamentalDiagram {
pub fn speed(&self, density_per_km: f64, free_flow_speed: f64, jam_density: f64) -> f64 {
if density_per_km <= 0.0 || jam_density <= 0.0 {
return free_flow_speed;
}
let speed = match self {
FundamentalDiagram::Greenshields => {
free_flow_speed * (1.0 - density_per_km / jam_density).max(0.0)
}
FundamentalDiagram::Underwood { k_opt } => {
let k_opt_val = k_opt.unwrap_or(jam_density / std::f64::consts::E);
if k_opt_val <= 0.0 {
free_flow_speed
} else {
free_flow_speed * (-density_per_km / k_opt_val).exp()
}
}
FundamentalDiagram::Triangular => {
let k_c = jam_density / 2.0;
if density_per_km <= k_c {
free_flow_speed
} else {
free_flow_speed * (jam_density - density_per_km) / (jam_density - k_c)
}
.max(0.0)
}
FundamentalDiagram::FreeFlow => free_flow_speed,
FundamentalDiagram::Weidmann => {
crate::pedestrian_links::weidmann_speed(free_flow_speed, density_per_km)
}
};
speed.max(0.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LinkProperties {
pub free_flow_speed: f64,
pub capacity: f64,
pub lanes: u32,
pub jam_density: f64,
pub diagram: FundamentalDiagram,
}
impl LinkProperties {
pub fn urban(
length: f64,
speed_kmh: f64,
lanes: u32,
) -> Result<(LinkGeometry, Self), LinkGeometryError> {
Ok((
LinkGeometry::new(length)?,
Self {
free_flow_speed: speed_kmh / 3.6,
capacity: 900.0 * lanes as f64,
lanes,
jam_density: 150.0,
diagram: FundamentalDiagram::Greenshields,
},
))
}
pub fn freeway(
length: f64,
speed_kmh: f64,
lanes: u32,
) -> Result<(LinkGeometry, Self), LinkGeometryError> {
Ok((
LinkGeometry::new(length)?,
Self {
free_flow_speed: speed_kmh / 3.6,
capacity: 2200.0 * lanes as f64,
lanes,
jam_density: 120.0,
diagram: FundamentalDiagram::Underwood { k_opt: None },
},
))
}
pub fn pedestrian(length: f64, width: f64) -> Result<(LinkGeometry, Self), LinkGeometryError> {
Ok((
LinkGeometry::new(length)?,
Self {
free_flow_speed: 1.3,
capacity: 4800.0 * width,
lanes: (width.ceil() as u32).max(1),
jam_density: 5000.0,
diagram: FundamentalDiagram::Greenshields,
},
))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TransportLinkMetadata {
pub edge_id: EdgeId,
pub from_node: NodeId,
pub to_node: NodeId,
pub link_class: LinkClass,
pub allowed_modes: AllowedModes,
pub traffic_control: Option<TrafficControlType>,
pub turn_type: Option<TurnType>,
pub speed_limit_kph: Option<f64>,
}
impl TransportLinkMetadata {
pub fn new(
edge_id: EdgeId,
from_node: NodeId,
to_node: NodeId,
link_class: LinkClass,
allowed_modes: AllowedModes,
) -> Self {
Self {
edge_id,
from_node,
to_node,
link_class,
allowed_modes,
traffic_control: None,
turn_type: None,
speed_limit_kph: None,
}
}
pub fn with_traffic_control(mut self, traffic_control: TrafficControlType) -> Self {
self.traffic_control = Some(traffic_control);
self
}
pub fn with_turn_type(mut self, turn_type: TurnType) -> Self {
self.turn_type = Some(turn_type);
self
}
pub fn with_speed_limit_kph(mut self, speed_limit_kph: f64) -> Self {
self.speed_limit_kph = Some(speed_limit_kph);
self
}
}
impl SemanticEntity for TransportLinkMetadata {
type Id = EdgeId;
fn semantic_id(&self) -> Self::Id {
self.edge_id
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TransitStopMetadata {
pub node_id: NodeId,
pub zone_id: Option<ZoneId>,
pub level_relation: Option<LevelRelation>,
pub served_modes: AllowedModes,
}
impl TransitStopMetadata {
pub fn new(node_id: NodeId, served_modes: AllowedModes) -> Self {
Self {
node_id,
zone_id: None,
level_relation: None,
served_modes,
}
}
pub fn with_zone(mut self, zone_id: ZoneId) -> Self {
self.zone_id = Some(zone_id);
self
}
pub fn with_level_relation(mut self, level_relation: LevelRelation) -> Self {
self.level_relation = Some(level_relation);
self
}
}
impl SemanticEntity for TransitStopMetadata {
type Id = NodeId;
fn semantic_id(&self) -> Self::Id {
self.node_id
}
}
pub type TransportLinkSpace = LinkSpace<LinkProperties>;
pub trait TransportLinkOps {
fn link_density(&self, link_id: LinkId) -> f64;
fn link_speed(&self, link_id: LinkId) -> f64;
fn link_free_flow_time(&self, link_id: LinkId) -> f64;
fn link_travel_time(&self, link_id: LinkId) -> f64;
fn agent_speed(&self, id: u64) -> Result<f64, LinkSpaceError>;
fn agent_speed_decision<P: QueuePolicy>(
&self,
id: u64,
policy: &P,
) -> Result<SpeedDecision, LinkSpaceError>;
fn agent_speed_with_policy<P: QueuePolicy>(
&self,
id: u64,
policy: &P,
) -> Result<f64, LinkSpaceError>;
fn volume_capacity_ratio(&self, link_id: LinkId, time_window_s: f64) -> f64;
}
impl TransportLinkOps for LinkSpace<LinkProperties> {
fn link_density(&self, link_id: LinkId) -> f64 {
let Some(props) = self.link_properties(link_id) else {
return 0.0;
};
let Some(length) = self.link_length(link_id) else {
return 0.0;
};
let n = self.agents_on_link(link_id) as f64;
let length_km = length / 1000.0;
let lanes = props.lanes.max(1) as f64;
if length_km <= 0.0 {
return 0.0;
}
n / (length_km * lanes)
}
fn link_speed(&self, link_id: LinkId) -> f64 {
let Some(props) = self.link_properties(link_id) else {
return 0.0;
};
props.diagram.speed(
self.link_density(link_id),
props.free_flow_speed,
props.jam_density,
)
}
fn link_free_flow_time(&self, link_id: LinkId) -> f64 {
let Some(props) = self.link_properties(link_id) else {
return f64::INFINITY;
};
let Some(length) = self.link_length(link_id) else {
return f64::INFINITY;
};
length / props.free_flow_speed
}
fn link_travel_time(&self, link_id: LinkId) -> f64 {
let speed = self.link_speed(link_id);
if speed <= 0.0 {
return f64::INFINITY;
}
let Some(length) = self.link_length(link_id) else {
return f64::INFINITY;
};
length / speed
}
fn agent_speed(&self, id: u64) -> Result<f64, LinkSpaceError> {
self.agent_speed_with_policy(id, &FifoGapPolicy::default())
}
fn agent_speed_decision<P: QueuePolicy>(
&self,
id: u64,
policy: &P,
) -> Result<SpeedDecision, LinkSpaceError> {
policy.speed_for(self, id)
}
fn agent_speed_with_policy<P: QueuePolicy>(
&self,
id: u64,
policy: &P,
) -> Result<f64, LinkSpaceError> {
Ok(self.agent_speed_decision(id, policy)?.speed)
}
fn volume_capacity_ratio(&self, link_id: LinkId, time_window_s: f64) -> f64 {
let Some(props) = self.link_properties(link_id) else {
return 0.0;
};
let count = self.agents_on_link(link_id) as f64;
let flow_rate = count / (time_window_s / 3600.0);
flow_rate / props.capacity
}
}
#[cfg(test)]
mod tests {
use super::*;
use rustsim_modes::TravelMode;
#[test]
fn transport_link_metadata_builder_helpers() {
let link = TransportLinkMetadata::new(7, 1, 2, LinkClass::Road, AllowedModes::vehicular())
.with_traffic_control(TrafficControlType::Signal)
.with_turn_type(TurnType::Through)
.with_speed_limit_kph(50.0);
assert_eq!(link.semantic_id(), 7);
assert_eq!(link.from_node, 1);
assert_eq!(link.to_node, 2);
assert_eq!(link.link_class, LinkClass::Road);
assert!(link.allowed_modes.allows(TravelMode::Vehicle));
assert!(link.allowed_modes.allows(TravelMode::Transit));
assert_eq!(link.traffic_control, Some(TrafficControlType::Signal));
assert_eq!(link.turn_type, Some(TurnType::Through));
assert_eq!(link.speed_limit_kph, Some(50.0));
}
#[test]
fn transit_stop_metadata_builder_helpers() {
let stop = TransitStopMetadata::new(3, AllowedModes::none().with_mode(TravelMode::Transit))
.with_zone(10)
.with_level_relation(LevelRelation::on(2));
assert_eq!(stop.semantic_id(), 3);
assert_eq!(stop.zone_id, Some(10));
assert_eq!(stop.level_relation, Some(LevelRelation::on(2)));
assert!(stop.served_modes.allows(TravelMode::Transit));
}
#[test]
fn fundamental_diagram_and_link_ops_work() {
let fd = FundamentalDiagram::Greenshields;
assert!((fd.speed(75.0, 13.9, 150.0) - 6.95).abs() < 0.1);
let mut space: TransportLinkSpace = LinkSpace::new();
let a = space.add_node();
let b = space.add_node();
let (geom, props) = LinkProperties::urban(500.0, 50.0, 2).unwrap();
let link = space.add_link(a, b, geom, props).unwrap();
for i in 1..=15 {
space.add_agent_to_link(i, link, (i as f64) * 30.0).unwrap();
}
assert!((space.link_density(link) - 15.0).abs() < 1e-6);
assert!((space.link_speed(link) - 12.51).abs() < 0.1);
let ff_time = space.link_free_flow_time(link);
assert!((ff_time - 36.0).abs() < 1.0);
assert!(space.volume_capacity_ratio(link, 3600.0) > 0.0);
}
}