1use std::collections::hash_map::Entry;
10use std::collections::{HashMap, HashSet, VecDeque};
11
12use crate::components::route::{Route, RouteLeg, TransportMode};
13use crate::dispatch::ElevatorGroup;
14use crate::entity::EntityId;
15use crate::ids::GroupId;
16
17#[derive(Debug, Clone)]
19struct Edge {
20 to: EntityId,
22 group: GroupId,
24}
25
26#[derive(Debug)]
31pub struct TopologyGraph {
32 dirty: bool,
34 adjacency: HashMap<EntityId, Vec<Edge>>,
36}
37
38impl TopologyGraph {
39 #[must_use]
41 pub fn new() -> Self {
42 Self {
43 dirty: true,
44 adjacency: HashMap::new(),
45 }
46 }
47
48 pub const fn mark_dirty(&mut self) {
50 self.dirty = true;
51 }
52
53 #[must_use]
55 pub const fn is_dirty(&self) -> bool {
56 self.dirty
57 }
58
59 pub fn rebuild(&mut self, groups: &[ElevatorGroup]) {
61 self.adjacency.clear();
62
63 for group in groups {
64 for line_info in group.lines() {
65 for &from in line_info.serves() {
67 for &to in line_info.serves() {
68 if from != to {
69 self.adjacency.entry(from).or_default().push(Edge {
70 to,
71 group: group.id(),
72 });
73 }
74 }
75 }
76 }
77 }
78
79 self.dirty = false;
80 }
81
82 #[must_use]
87 pub fn reachable_stops_from(&self, stop: EntityId) -> Vec<EntityId> {
88 let mut visited = HashSet::new();
89 let mut queue = VecDeque::new();
90
91 visited.insert(stop);
92 queue.push_back(stop);
93
94 while let Some(current) = queue.pop_front() {
95 if let Some(edges) = self.adjacency.get(¤t) {
96 for edge in edges {
97 if visited.insert(edge.to) {
98 queue.push_back(edge.to);
99 }
100 }
101 }
102 }
103
104 visited.remove(&stop);
106 visited.into_iter().collect()
107 }
108
109 #[must_use]
116 pub fn transfer_points(groups: &[ElevatorGroup]) -> Vec<EntityId> {
117 let mut stop_groups: HashMap<EntityId, usize> = HashMap::new();
118 for group in groups {
119 for &stop in group.stop_entities() {
120 *stop_groups.entry(stop).or_insert(0) += 1;
121 }
122 }
123 stop_groups
124 .into_iter()
125 .filter(|&(_, count)| count >= 2)
126 .map(|(stop, _)| stop)
127 .collect()
128 }
129
130 #[must_use]
138 pub fn shortest_route(&self, from: EntityId, to: EntityId) -> Option<Route> {
139 if from == to {
140 return None;
141 }
142
143 let mut visited: HashMap<EntityId, (EntityId, GroupId)> = HashMap::new();
145 let mut queue = VecDeque::new();
146
147 queue.push_back(from);
148 visited.insert(from, (from, GroupId(u32::MAX)));
150
151 while let Some(current) = queue.pop_front() {
152 if current == to {
153 break;
154 }
155 if let Some(edges) = self.adjacency.get(¤t) {
156 for edge in edges {
157 if let Entry::Vacant(e) = visited.entry(edge.to) {
158 e.insert((current, edge.group));
159 queue.push_back(edge.to);
160 }
161 }
162 }
163 }
164
165 if !visited.contains_key(&to) {
166 return None;
167 }
168
169 let mut path: Vec<(EntityId, GroupId)> = Vec::new();
171 let mut current = to;
172 while current != from {
173 let &(prev, group) = visited.get(¤t)?;
174 path.push((current, group));
175 current = prev;
176 }
177 path.reverse();
178
179 let mut legs = Vec::with_capacity(path.len());
181 let mut leg_from = from;
182 for (stop, group) in path {
183 legs.push(RouteLeg {
184 from: leg_from,
185 to: stop,
186 via: TransportMode::Group(group),
187 });
188 leg_from = stop;
189 }
190
191 Some(Route {
192 legs,
193 current_leg: 0,
194 })
195 }
196}
197
198impl Default for TopologyGraph {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204#[cfg(test)]
205#[allow(clippy::expect_used, clippy::panic)]
206mod tests {
207 use super::*;
208 use crate::dispatch::LineInfo;
209 use slotmap::KeyData;
210
211 fn eid(n: u64) -> EntityId {
213 EntityId::from(KeyData::from_ffi(n))
214 }
215
216 fn group_with_line(
218 group_id: u32,
219 line_entity: EntityId,
220 stops: Vec<EntityId>,
221 ) -> ElevatorGroup {
222 ElevatorGroup::new(
223 GroupId(group_id),
224 format!("Group {group_id}"),
225 vec![LineInfo::new(line_entity, Vec::new(), stops)],
226 )
227 }
228
229 #[test]
230 fn rebuild_clears_dirty_flag() {
231 let mut graph = TopologyGraph::new();
232 assert!(graph.is_dirty());
233 graph.rebuild(&[]);
234 assert!(!graph.is_dirty());
235 }
236
237 #[test]
238 fn mark_dirty_sets_flag() {
239 let mut graph = TopologyGraph::new();
240 graph.rebuild(&[]);
241 graph.mark_dirty();
242 assert!(graph.is_dirty());
243 }
244
245 #[test]
246 fn reachable_within_single_line() {
247 let a = eid(1);
248 let b = eid(2);
249 let c = eid(3);
250 let line = eid(100);
251
252 let groups = vec![group_with_line(0, line, vec![a, b, c])];
253 let mut graph = TopologyGraph::new();
254 graph.rebuild(&groups);
255
256 let mut reachable = graph.reachable_stops_from(a);
257 reachable.sort();
258 let mut expected = vec![b, c];
259 expected.sort();
260 assert_eq!(reachable, expected);
261 }
262
263 #[test]
264 fn reachable_across_groups_via_transfer() {
265 let a = eid(1);
266 let b = eid(2); let c = eid(3);
268 let line0 = eid(100);
269 let line1 = eid(101);
270
271 let groups = vec![
272 group_with_line(0, line0, vec![a, b]),
273 group_with_line(1, line1, vec![b, c]),
274 ];
275 let mut graph = TopologyGraph::new();
276 graph.rebuild(&groups);
277
278 let mut reachable = graph.reachable_stops_from(a);
279 reachable.sort();
280 let mut expected = vec![b, c];
281 expected.sort();
282 assert_eq!(reachable, expected);
283 }
284
285 #[test]
286 fn unreachable_stop() {
287 let a = eid(1);
288 let b = eid(2);
289 let c = eid(3);
290 let line0 = eid(100);
291 let line1 = eid(101);
292
293 let groups = vec![
295 group_with_line(0, line0, vec![a]),
296 group_with_line(1, line1, vec![b, c]),
297 ];
298 let mut graph = TopologyGraph::new();
299 graph.rebuild(&groups);
300
301 let reachable = graph.reachable_stops_from(a);
302 assert!(reachable.is_empty());
303 }
304
305 #[test]
306 fn transfer_points_detected() {
307 let a = eid(1);
308 let b = eid(2);
309 let c = eid(3);
310 let line0 = eid(100);
311 let line1 = eid(101);
312
313 let groups = vec![
314 group_with_line(0, line0, vec![a, b]),
315 group_with_line(1, line1, vec![b, c]),
316 ];
317
318 let transfers = TopologyGraph::transfer_points(&groups);
319 assert_eq!(transfers, vec![b]);
320 }
321
322 #[test]
323 fn shortest_route_direct() {
324 let a = eid(1);
325 let b = eid(2);
326 let line = eid(100);
327
328 let groups = vec![group_with_line(0, line, vec![a, b])];
329 let mut graph = TopologyGraph::new();
330 graph.rebuild(&groups);
331
332 let route = graph.shortest_route(a, b).expect("expected direct route");
333 assert_eq!(route.legs.len(), 1);
334 assert_eq!(route.legs[0].from, a);
335 assert_eq!(route.legs[0].to, b);
336 assert_eq!(route.legs[0].via, TransportMode::Group(GroupId(0)));
337 }
338
339 #[test]
340 fn shortest_route_with_transfer() {
341 let a = eid(1);
342 let b = eid(2);
343 let c = eid(3);
344 let line0 = eid(100);
345 let line1 = eid(101);
346
347 let groups = vec![
348 group_with_line(0, line0, vec![a, b]),
349 group_with_line(1, line1, vec![b, c]),
350 ];
351 let mut graph = TopologyGraph::new();
352 graph.rebuild(&groups);
353
354 let route = graph.shortest_route(a, c).expect("expected transfer route");
355 assert_eq!(route.legs.len(), 2);
356 assert_eq!(route.legs[0].from, a);
357 assert_eq!(route.legs[0].to, b);
358 assert_eq!(route.legs[0].via, TransportMode::Group(GroupId(0)));
359 assert_eq!(route.legs[1].from, b);
360 assert_eq!(route.legs[1].to, c);
361 assert_eq!(route.legs[1].via, TransportMode::Group(GroupId(1)));
362 }
363
364 #[test]
365 fn shortest_route_unreachable() {
366 let a = eid(1);
367 let b = eid(2);
368 let line0 = eid(100);
369 let line1 = eid(101);
370
371 let groups = vec![
372 group_with_line(0, line0, vec![a]),
373 group_with_line(1, line1, vec![b]),
374 ];
375 let mut graph = TopologyGraph::new();
376 graph.rebuild(&groups);
377
378 assert!(graph.shortest_route(a, b).is_none());
379 }
380
381 #[test]
382 fn shortest_route_same_stop() {
383 let a = eid(1);
384 let line = eid(100);
385
386 let groups = vec![group_with_line(0, line, vec![a])];
387 let mut graph = TopologyGraph::new();
388 graph.rebuild(&groups);
389
390 assert!(graph.shortest_route(a, a).is_none());
391 }
392}