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