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]
120 pub fn transfer_points(groups: &[ElevatorGroup]) -> Vec<EntityId> {
121 let mut stop_groups: HashMap<EntityId, usize> = HashMap::new();
122 for group in groups {
123 for &stop in group.stop_entities() {
124 *stop_groups.entry(stop).or_insert(0) += 1;
125 }
126 }
127 stop_groups
128 .into_iter()
129 .filter(|&(_, count)| count >= 2)
130 .map(|(stop, _)| stop)
131 .collect()
132 }
133
134 #[must_use]
142 pub fn shortest_route(&self, from: EntityId, to: EntityId) -> Option<Route> {
143 if from == to {
144 return None;
145 }
146
147 let mut visited: HashMap<EntityId, (EntityId, GroupId)> = HashMap::new();
149 let mut queue = VecDeque::new();
150
151 queue.push_back(from);
152 visited.insert(from, (from, GroupId(u32::MAX)));
154
155 while let Some(current) = queue.pop_front() {
156 if current == to {
157 break;
158 }
159 if let Some(edges) = self.adjacency.get(¤t) {
160 for edge in edges {
161 if let Entry::Vacant(e) = visited.entry(edge.to) {
162 e.insert((current, edge.group));
163 queue.push_back(edge.to);
164 }
165 }
166 }
167 }
168
169 if !visited.contains_key(&to) {
171 return None;
172 }
173
174 let mut path: Vec<(EntityId, GroupId)> = Vec::new();
176 let mut current = to;
177 while current != from {
178 let &(prev, group) = visited.get(¤t)?;
179 path.push((current, group));
180 current = prev;
181 }
182 path.reverse();
183
184 let mut legs = Vec::with_capacity(path.len());
186 let mut leg_from = from;
187 for (stop, group) in path {
188 legs.push(RouteLeg {
189 from: leg_from,
190 to: stop,
191 via: TransportMode::Group(group),
192 });
193 leg_from = stop;
194 }
195
196 Some(Route {
197 legs,
198 current_leg: 0,
199 })
200 }
201}
202
203impl Default for TopologyGraph {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209#[cfg(test)]
210#[allow(clippy::expect_used, clippy::panic)]
211mod tests {
212 use super::*;
213 use crate::dispatch::LineInfo;
214 use slotmap::KeyData;
215
216 fn eid(n: u64) -> EntityId {
218 EntityId::from(KeyData::from_ffi(n))
219 }
220
221 fn group_with_line(
223 group_id: u32,
224 line_entity: EntityId,
225 stops: Vec<EntityId>,
226 ) -> ElevatorGroup {
227 ElevatorGroup::new(
228 GroupId(group_id),
229 format!("Group {group_id}"),
230 vec![LineInfo::new(line_entity, Vec::new(), stops)],
231 )
232 }
233
234 #[test]
235 fn rebuild_clears_dirty_flag() {
236 let mut graph = TopologyGraph::new();
237 assert!(graph.is_dirty());
238 graph.rebuild(&[]);
239 assert!(!graph.is_dirty());
240 }
241
242 #[test]
243 fn mark_dirty_sets_flag() {
244 let mut graph = TopologyGraph::new();
245 graph.rebuild(&[]);
246 graph.mark_dirty();
247 assert!(graph.is_dirty());
248 }
249
250 #[test]
251 fn reachable_within_single_line() {
252 let a = eid(1);
253 let b = eid(2);
254 let c = eid(3);
255 let line = eid(100);
256
257 let groups = vec![group_with_line(0, line, vec![a, b, c])];
258 let mut graph = TopologyGraph::new();
259 graph.rebuild(&groups);
260
261 let mut reachable = graph.reachable_stops_from(a);
262 reachable.sort();
263 let mut expected = vec![b, c];
264 expected.sort();
265 assert_eq!(reachable, expected);
266 }
267
268 #[test]
269 fn reachable_across_groups_via_transfer() {
270 let a = eid(1);
271 let b = eid(2); let c = eid(3);
273 let line0 = eid(100);
274 let line1 = eid(101);
275
276 let groups = vec![
277 group_with_line(0, line0, vec![a, b]),
278 group_with_line(1, line1, vec![b, c]),
279 ];
280 let mut graph = TopologyGraph::new();
281 graph.rebuild(&groups);
282
283 let mut reachable = graph.reachable_stops_from(a);
284 reachable.sort();
285 let mut expected = vec![b, c];
286 expected.sort();
287 assert_eq!(reachable, expected);
288 }
289
290 #[test]
291 fn unreachable_stop() {
292 let a = eid(1);
293 let b = eid(2);
294 let c = eid(3);
295 let line0 = eid(100);
296 let line1 = eid(101);
297
298 let groups = vec![
300 group_with_line(0, line0, vec![a]),
301 group_with_line(1, line1, vec![b, c]),
302 ];
303 let mut graph = TopologyGraph::new();
304 graph.rebuild(&groups);
305
306 let reachable = graph.reachable_stops_from(a);
307 assert!(reachable.is_empty());
308 }
309
310 #[test]
311 fn transfer_points_detected() {
312 let a = eid(1);
313 let b = eid(2);
314 let c = eid(3);
315 let line0 = eid(100);
316 let line1 = eid(101);
317
318 let groups = vec![
319 group_with_line(0, line0, vec![a, b]),
320 group_with_line(1, line1, vec![b, c]),
321 ];
322
323 let transfers = TopologyGraph::transfer_points(&groups);
324 assert_eq!(transfers, vec![b]);
325 }
326
327 #[test]
328 fn shortest_route_direct() {
329 let a = eid(1);
330 let b = eid(2);
331 let line = eid(100);
332
333 let groups = vec![group_with_line(0, line, vec![a, b])];
334 let mut graph = TopologyGraph::new();
335 graph.rebuild(&groups);
336
337 let route = graph.shortest_route(a, b).expect("expected direct route");
338 assert_eq!(route.legs.len(), 1);
339 assert_eq!(route.legs[0].from, a);
340 assert_eq!(route.legs[0].to, b);
341 assert_eq!(route.legs[0].via, TransportMode::Group(GroupId(0)));
342 }
343
344 #[test]
345 fn shortest_route_with_transfer() {
346 let a = eid(1);
347 let b = eid(2);
348 let c = eid(3);
349 let line0 = eid(100);
350 let line1 = eid(101);
351
352 let groups = vec![
353 group_with_line(0, line0, vec![a, b]),
354 group_with_line(1, line1, vec![b, c]),
355 ];
356 let mut graph = TopologyGraph::new();
357 graph.rebuild(&groups);
358
359 let route = graph.shortest_route(a, c).expect("expected transfer route");
360 assert_eq!(route.legs.len(), 2);
361 assert_eq!(route.legs[0].from, a);
362 assert_eq!(route.legs[0].to, b);
363 assert_eq!(route.legs[0].via, TransportMode::Group(GroupId(0)));
364 assert_eq!(route.legs[1].from, b);
365 assert_eq!(route.legs[1].to, c);
366 assert_eq!(route.legs[1].via, TransportMode::Group(GroupId(1)));
367 }
368
369 #[test]
370 fn shortest_route_unreachable() {
371 let a = eid(1);
372 let b = eid(2);
373 let line0 = eid(100);
374 let line1 = eid(101);
375
376 let groups = vec![
377 group_with_line(0, line0, vec![a]),
378 group_with_line(1, line1, vec![b]),
379 ];
380 let mut graph = TopologyGraph::new();
381 graph.rebuild(&groups);
382
383 assert!(graph.shortest_route(a, b).is_none());
384 }
385
386 #[test]
387 fn shortest_route_same_stop() {
388 let a = eid(1);
389 let line = eid(100);
390
391 let groups = vec![group_with_line(0, line, vec![a])];
392 let mut graph = TopologyGraph::new();
393 graph.rebuild(&groups);
394
395 assert!(graph.shortest_route(a, a).is_none());
396 }
397}