rust_igraph/algorithms/properties/
topological_sorting.rs1use std::collections::VecDeque;
19
20use crate::algorithms::paths::dijkstra::DijkstraMode;
21use crate::core::Graph;
22use crate::core::error::{IgraphError, IgraphResult};
23use crate::core::graph::VertexId;
24
25pub fn topological_sorting(graph: &Graph, mode: DijkstraMode) -> IgraphResult<Vec<VertexId>> {
64 if !graph.is_directed() {
65 return Err(IgraphError::InvalidArgument(
66 "topological_sorting requires a directed graph".to_string(),
67 ));
68 }
69 if matches!(mode, DijkstraMode::All) {
70 return Err(IgraphError::InvalidArgument(
71 "topological_sorting does not accept DijkstraMode::All".to_string(),
72 ));
73 }
74 let walk_forward = matches!(mode, DijkstraMode::Out);
75
76 let n = graph.vcount();
77 let n_us = n as usize;
78
79 let mut degrees: Vec<u32> = vec![0; n_us];
84 for v in 0..n {
85 let incidents = if walk_forward {
86 graph.incident_in(v)?
87 } else {
88 graph.incident(v)?
89 };
90 let mut count: u32 = 0;
91 for eid in incidents {
92 let other = graph.edge_other(eid, v)?;
93 if other != v {
94 count = count.saturating_add(1);
95 }
96 }
97 degrees[v as usize] = count;
98 }
99
100 let mut sources: VecDeque<VertexId> = VecDeque::new();
102 for v in 0..n {
103 if degrees[v as usize] == 0 {
104 sources.push_back(v);
105 }
106 }
107
108 let mut order: Vec<VertexId> = Vec::with_capacity(n_us);
109 while let Some(v) = sources.pop_front() {
110 order.push(v);
111 let outgoing = if walk_forward {
115 graph.incident(v)?
116 } else {
117 graph.incident_in(v)?
118 };
119 for eid in outgoing {
120 let nei = graph.edge_other(eid, v)?;
121 if nei == v {
122 continue;
124 }
125 let nei_idx = nei as usize;
126 degrees[nei_idx] = degrees[nei_idx].saturating_sub(1);
127 if degrees[nei_idx] == 0 {
128 sources.push_back(nei);
129 }
130 }
131 }
132
133 if order.len() != n_us {
134 return Err(IgraphError::InvalidArgument(
135 "topological_sorting: graph contains a cycle (no valid ordering exists)".to_string(),
136 ));
137 }
138 Ok(order)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::core::Graph;
145
146 #[test]
147 fn empty_directed_graph_returns_empty_order() {
148 let g = Graph::new(0, true).unwrap();
149 let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
150 assert!(order.is_empty());
151 }
152
153 #[test]
154 fn isolated_vertices_only_returns_all_in_some_order() {
155 let g = Graph::new(3, true).unwrap();
156 let mut order = topological_sorting(&g, DijkstraMode::Out).unwrap();
157 order.sort_unstable();
158 assert_eq!(order, vec![0, 1, 2]);
159 }
160
161 #[test]
162 fn linear_chain_out_mode_preserves_order() {
163 let mut g = Graph::new(4, true).unwrap();
165 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
166 let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
167 assert_eq!(order, vec![0, 1, 2, 3]);
168 }
169
170 #[test]
171 fn linear_chain_in_mode_reverses_order() {
172 let mut g = Graph::new(4, true).unwrap();
174 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
175 let order = topological_sorting(&g, DijkstraMode::In).unwrap();
176 assert_eq!(order, vec![3, 2, 1, 0]);
177 }
178
179 #[test]
180 fn diamond_dag_respects_edge_order() {
181 let mut g = Graph::new(4, true).unwrap();
183 g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3), (2, 3)])
184 .unwrap();
185 let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
186 assert_eq!(order[0], 0);
188 assert_eq!(order[3], 3);
189 assert!(
190 order == vec![0, 1, 2, 3] || order == vec![0, 2, 1, 3],
191 "unexpected order: {order:?}"
192 );
193 }
194
195 #[test]
196 fn self_loop_does_not_block_sorting() {
197 let mut g = Graph::new(2, true).unwrap();
199 g.add_edge(0, 0).unwrap();
200 g.add_edge(0, 1).unwrap();
201 let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
202 assert_eq!(order, vec![0, 1]);
203 }
204
205 #[test]
206 fn three_cycle_errors() {
207 let mut g = Graph::new(3, true).unwrap();
208 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
209 let err = topological_sorting(&g, DijkstraMode::Out).unwrap_err();
210 assert!(matches!(err, IgraphError::InvalidArgument(_)));
211 }
212
213 #[test]
214 fn undirected_graph_errors() {
215 let mut g = Graph::with_vertices(2);
216 g.add_edge(0, 1).unwrap();
217 let err = topological_sorting(&g, DijkstraMode::Out).unwrap_err();
218 assert!(matches!(err, IgraphError::InvalidArgument(_)));
219 }
220
221 #[test]
222 fn all_mode_errors() {
223 let mut g = Graph::new(2, true).unwrap();
224 g.add_edge(0, 1).unwrap();
225 let err = topological_sorting(&g, DijkstraMode::All).unwrap_err();
226 assert!(matches!(err, IgraphError::InvalidArgument(_)));
227 }
228
229 #[test]
230 fn parallel_edges_dont_block_ordering() {
231 let mut g = Graph::new(2, true).unwrap();
233 g.add_edge(0, 1).unwrap();
234 g.add_edge(0, 1).unwrap();
235 let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
236 assert_eq!(order, vec![0, 1]);
237 }
238
239 #[test]
240 fn topological_order_respects_every_edge() {
241 let mut g = Graph::new(5, true).unwrap();
244 g.add_edges(vec![(0u32, 2u32), (1, 2), (2, 3), (2, 4)])
245 .unwrap();
246 let order = topological_sorting(&g, DijkstraMode::Out).unwrap();
247 let pos: Vec<usize> = {
248 let mut p = vec![0usize; 5];
249 for (i, &v) in order.iter().enumerate() {
250 p[v as usize] = i;
251 }
252 p
253 };
254 let m = u32::try_from(g.ecount()).expect("ecount fits in u32 in test");
256 for e in 0..m {
257 let (u, v) = g.edge(e).unwrap();
258 if u == v {
259 continue;
260 }
261 assert!(
262 pos[u as usize] < pos[v as usize],
263 "edge {u}→{v} violates order"
264 );
265 }
266 }
267
268 #[test]
269 fn cycle_with_tail_still_errors() {
270 let mut g = Graph::new(4, true).unwrap();
273 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0), (3, 0)])
274 .unwrap();
275 let err = topological_sorting(&g, DijkstraMode::Out).unwrap_err();
276 assert!(matches!(err, IgraphError::InvalidArgument(_)));
277 }
278}