rust_igraph/algorithms/connectivity/
decompose.rs1use std::collections::VecDeque;
16
17use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
18
19pub fn decompose(graph: &Graph) -> IgraphResult<Vec<Graph>> {
56 let n = graph.vcount();
57 if n == 0 {
58 return Ok(Vec::new());
59 }
60
61 let mut vid_in_comp = vec![u32::MAX; n as usize];
64 let mut comp_of = vec![u32::MAX; n as usize];
65 let mut comp_vertices: Vec<Vec<VertexId>> = Vec::new();
67 let mut queue: VecDeque<VertexId> = VecDeque::new();
68
69 for actstart in 0..n {
70 if vid_in_comp[actstart as usize] != u32::MAX {
71 continue;
72 }
73 let comp_id = u32::try_from(comp_vertices.len())
74 .map_err(|_| IgraphError::Internal("too many components in decompose"))?;
75 let mut verts: Vec<VertexId> = Vec::new();
76
77 vid_in_comp[actstart as usize] = 0;
78 comp_of[actstart as usize] = comp_id;
79 verts.push(actstart);
80 queue.clear();
81 queue.push_back(actstart);
82
83 while let Some(v) = queue.pop_front() {
84 for w in graph.neighbors_iter(v)? {
85 if vid_in_comp[w as usize] == u32::MAX {
86 let new_local = u32::try_from(verts.len())
87 .map_err(|_| IgraphError::Internal("component too large in decompose"))?;
88 vid_in_comp[w as usize] = new_local;
89 comp_of[w as usize] = comp_id;
90 verts.push(w);
91 queue.push_back(w);
92 }
93 }
94 }
95 comp_vertices.push(verts);
96 }
97
98 let directed = graph.is_directed();
100 let mut parts: Vec<Graph> = comp_vertices
101 .iter()
102 .map(|verts| {
103 let k = u32::try_from(verts.len())
105 .map_err(|_| IgraphError::Internal("component vertex count exceeds u32"))?;
106 Graph::new(k, directed)
107 })
108 .collect::<IgraphResult<Vec<_>>>()?;
109
110 let m = graph.ecount();
115 for e in 0..m {
116 let eid = u32::try_from(e)
117 .map_err(|_| IgraphError::Internal("edge id exceeds u32 in decompose"))?;
118 let s = graph.edge_source(eid)?;
119 let t = graph.edge_target(eid)?;
120 let comp_s = comp_of[s as usize];
121 debug_assert_eq!(comp_s, comp_of[t as usize]);
123 let local_s = vid_in_comp[s as usize];
124 let local_t = vid_in_comp[t as usize];
125 parts[comp_s as usize].add_edge(local_s, local_t)?;
126 }
127
128 Ok(parts)
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn empty_graph_yields_no_components() {
137 let g = Graph::with_vertices(0);
138 let parts = decompose(&g).unwrap();
139 assert!(parts.is_empty());
140 }
141
142 #[test]
143 fn null_graph_with_isolated_vertices() {
144 let g = Graph::with_vertices(3);
145 let parts = decompose(&g).unwrap();
146 assert_eq!(parts.len(), 3);
147 for p in &parts {
148 assert_eq!(p.vcount(), 1);
149 assert_eq!(p.ecount(), 0);
150 }
151 }
152
153 #[test]
154 fn single_component_preserves_size() {
155 let mut g = Graph::with_vertices(4);
156 g.add_edge(0, 1).unwrap();
157 g.add_edge(1, 2).unwrap();
158 g.add_edge(2, 3).unwrap();
159 let parts = decompose(&g).unwrap();
160 assert_eq!(parts.len(), 1);
161 assert_eq!(parts[0].vcount(), 4);
162 assert_eq!(parts[0].ecount(), 3);
163 }
164
165 #[test]
166 fn two_disjoint_triangles() {
167 let mut g = Graph::with_vertices(6);
168 g.add_edge(0, 1).unwrap();
169 g.add_edge(1, 2).unwrap();
170 g.add_edge(2, 0).unwrap();
171 g.add_edge(3, 4).unwrap();
172 g.add_edge(4, 5).unwrap();
173 g.add_edge(5, 3).unwrap();
174 let parts = decompose(&g).unwrap();
175 assert_eq!(parts.len(), 2);
176 for p in &parts {
177 assert_eq!(p.vcount(), 3);
178 assert_eq!(p.ecount(), 3);
179 }
180 }
181
182 #[test]
183 fn vertex_remapping_starts_at_zero() {
184 let mut g = Graph::with_vertices(6);
187 g.add_edge(3, 4).unwrap();
188 g.add_edge(4, 5).unwrap();
189 g.add_edge(5, 3).unwrap();
190 let parts = decompose(&g).unwrap();
191 assert_eq!(parts.len(), 4);
193 for p in parts.iter().take(3) {
195 assert_eq!(p.vcount(), 1);
196 assert_eq!(p.ecount(), 0);
197 }
198 let tri = &parts[3];
200 assert_eq!(tri.vcount(), 3);
201 assert_eq!(tri.ecount(), 3);
202 assert_eq!(tri.edge_source(0).unwrap(), 0);
204 assert_eq!(tri.edge_target(0).unwrap(), 1);
205 }
206
207 #[test]
208 fn directed_graph_preserves_edge_orientation() {
209 let mut g = Graph::new(4, true).unwrap();
212 g.add_edge(0, 1).unwrap();
213 g.add_edge(1, 2).unwrap();
214 let parts = decompose(&g).unwrap();
215 assert_eq!(parts.len(), 2);
216 let chain = &parts[0];
217 assert!(chain.is_directed());
218 assert_eq!(chain.vcount(), 3);
219 assert_eq!(chain.ecount(), 2);
220 assert_eq!(chain.edge_source(0).unwrap(), 0);
221 assert_eq!(chain.edge_target(0).unwrap(), 1);
222 assert_eq!(chain.edge_source(1).unwrap(), 1);
223 assert_eq!(chain.edge_target(1).unwrap(), 2);
224 }
225
226 #[test]
227 fn loops_and_parallel_edges_preserved() {
228 let mut g = Graph::with_vertices(3);
229 g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap();
231 g.add_edge(0, 1).unwrap(); let parts = decompose(&g).unwrap();
233 assert_eq!(parts.len(), 2);
235 let main = &parts[0];
236 assert_eq!(main.vcount(), 2);
237 assert_eq!(main.ecount(), 3);
238 }
239
240 #[test]
241 fn component_count_matches_connected_components() {
242 let mut g = Graph::with_vertices(7);
244 g.add_edge(0, 1).unwrap();
245 g.add_edge(2, 3).unwrap();
246 g.add_edge(3, 4).unwrap();
247 let parts = decompose(&g).unwrap();
249 let cc = crate::algorithms::connectivity::components::connected_components(&g).unwrap();
250 assert_eq!(u32::try_from(parts.len()).unwrap(), cc.count);
251 }
252
253 #[test]
254 fn round_trip_edge_count_total_matches() {
255 let mut g = Graph::with_vertices(8);
259 g.add_edge(0, 1).unwrap();
260 g.add_edge(1, 2).unwrap();
261 g.add_edge(3, 4).unwrap();
262 g.add_edge(4, 5).unwrap();
263 g.add_edge(6, 7).unwrap();
264 let parts = decompose(&g).unwrap();
265 let total_e: usize = parts.iter().map(Graph::ecount).sum();
266 assert_eq!(total_e, g.ecount());
267 let total_v: u32 = parts.iter().map(Graph::vcount).sum();
268 assert_eq!(total_v, g.vcount());
269 }
270}