rust_igraph/algorithms/properties/
is_series_parallel.rs1use crate::core::{Graph, IgraphResult};
22
23pub fn is_series_parallel(graph: &Graph) -> IgraphResult<bool> {
52 let n = graph.vcount() as usize;
53 if n == 0 {
54 return Ok(true);
55 }
56
57 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
58 for v in 0..graph.vcount() {
59 let nbrs = graph.neighbors(v)?;
60 for &w in &nbrs {
61 let ww = w as usize;
62 let vv = v as usize;
63 if vv < ww {
64 adj[vv].push(ww);
65 adj[ww].push(vv);
66 }
67 }
68 }
69
70 Ok(sp_reduce(&mut adj, n))
71}
72
73fn sp_reduce(adj: &mut [Vec<usize>], n: usize) -> bool {
74 let mut alive = vec![true; n];
75 let mut changed = true;
76
77 while changed {
78 changed = false;
79
80 for v in 0..n {
82 if !alive[v] {
83 continue;
84 }
85 adj[v].sort_unstable();
86 let before = adj[v].len();
87 adj[v].dedup();
88 if adj[v].len() < before {
89 changed = true;
90 let nbrs: Vec<usize> = adj[v].clone();
92 for w in nbrs {
93 adj[w].sort_unstable();
94 adj[w].dedup();
95 }
96 }
97 }
98
99 for v in 0..n {
100 if !alive[v] {
101 continue;
102 }
103
104 let deg = adj[v].len();
105
106 if deg == 0 {
107 alive[v] = false;
108 changed = true;
109 continue;
110 }
111
112 if deg == 1 {
113 let w = adj[v][0];
114 adj[v].clear();
115 adj[w].retain(|&x| x != v);
116 alive[v] = false;
117 changed = true;
118 continue;
119 }
120
121 if deg == 2 && adj[v][0] != adj[v][1] {
122 let a = adj[v][0];
123 let b = adj[v][1];
124 adj[v].clear();
125 alive[v] = false;
126
127 adj[a].retain(|&x| x != v);
128 adj[b].retain(|&x| x != v);
129 adj[a].push(b);
130 adj[b].push(a);
131 changed = true;
132 }
133 }
134 }
135
136 let remaining_edges: usize = adj.iter().map(Vec::len).sum::<usize>() / 2;
137 let remaining_verts = alive.iter().filter(|&&a| a).count();
138
139 remaining_verts <= 2 && remaining_edges <= 1
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn empty_graph() {
148 let g = Graph::with_vertices(0);
149 assert!(is_series_parallel(&g).unwrap());
150 }
151
152 #[test]
153 fn single_vertex() {
154 let g = Graph::with_vertices(1);
155 assert!(is_series_parallel(&g).unwrap());
156 }
157
158 #[test]
159 fn single_edge() {
160 let mut g = Graph::with_vertices(2);
161 g.add_edge(0, 1).unwrap();
162 assert!(is_series_parallel(&g).unwrap());
163 }
164
165 #[test]
166 fn triangle() {
167 let mut g = Graph::with_vertices(3);
168 g.add_edge(0, 1).unwrap();
169 g.add_edge(1, 2).unwrap();
170 g.add_edge(2, 0).unwrap();
171 assert!(is_series_parallel(&g).unwrap());
172 }
173
174 #[test]
175 fn path() {
176 let mut g = Graph::with_vertices(5);
177 g.add_edge(0, 1).unwrap();
178 g.add_edge(1, 2).unwrap();
179 g.add_edge(2, 3).unwrap();
180 g.add_edge(3, 4).unwrap();
181 assert!(is_series_parallel(&g).unwrap());
182 }
183
184 #[test]
185 fn cycle_c4() {
186 let mut g = Graph::with_vertices(4);
187 g.add_edge(0, 1).unwrap();
188 g.add_edge(1, 2).unwrap();
189 g.add_edge(2, 3).unwrap();
190 g.add_edge(3, 0).unwrap();
191 assert!(is_series_parallel(&g).unwrap());
192 }
193
194 #[test]
195 fn cycle_c5() {
196 let mut g = Graph::with_vertices(5);
197 g.add_edge(0, 1).unwrap();
198 g.add_edge(1, 2).unwrap();
199 g.add_edge(2, 3).unwrap();
200 g.add_edge(3, 4).unwrap();
201 g.add_edge(4, 0).unwrap();
202 assert!(is_series_parallel(&g).unwrap());
203 }
204
205 #[test]
206 fn k4_not_sp() {
207 let mut g = Graph::with_vertices(4);
208 for i in 0..4u32 {
209 for j in (i + 1)..4 {
210 g.add_edge(i, j).unwrap();
211 }
212 }
213 assert!(!is_series_parallel(&g).unwrap());
214 }
215
216 #[test]
217 fn k5_not_sp() {
218 let mut g = Graph::with_vertices(5);
219 for i in 0..5u32 {
220 for j in (i + 1)..5 {
221 g.add_edge(i, j).unwrap();
222 }
223 }
224 assert!(!is_series_parallel(&g).unwrap());
225 }
226
227 #[test]
228 fn diamond_sp() {
229 let mut g = Graph::with_vertices(4);
232 g.add_edge(0, 1).unwrap();
233 g.add_edge(0, 2).unwrap();
234 g.add_edge(0, 3).unwrap();
235 g.add_edge(1, 2).unwrap();
236 g.add_edge(1, 3).unwrap();
237 assert!(is_series_parallel(&g).unwrap());
239 }
240
241 #[test]
242 fn wheatstone_bridge_sp() {
243 let mut g = Graph::with_vertices(4);
248 g.add_edge(0, 1).unwrap();
249 g.add_edge(0, 2).unwrap();
250 g.add_edge(1, 2).unwrap();
251 g.add_edge(1, 3).unwrap();
252 g.add_edge(2, 3).unwrap();
253 assert!(is_series_parallel(&g).unwrap());
254 }
255
256 #[test]
257 fn tree_sp() {
258 let mut g = Graph::with_vertices(6);
260 g.add_edge(0, 1).unwrap();
261 g.add_edge(1, 2).unwrap();
262 g.add_edge(1, 3).unwrap();
263 g.add_edge(3, 4).unwrap();
264 g.add_edge(3, 5).unwrap();
265 assert!(is_series_parallel(&g).unwrap());
266 }
267
268 #[test]
269 fn edgeless() {
270 let g = Graph::with_vertices(5);
271 assert!(is_series_parallel(&g).unwrap());
272 }
273
274 #[test]
275 fn star() {
276 let mut g = Graph::with_vertices(5);
277 g.add_edge(0, 1).unwrap();
278 g.add_edge(0, 2).unwrap();
279 g.add_edge(0, 3).unwrap();
280 g.add_edge(0, 4).unwrap();
281 assert!(is_series_parallel(&g).unwrap());
282 }
283
284 #[test]
285 fn petersen_not_sp() {
286 let mut g = Graph::with_vertices(10);
288 let edges = [
289 (0, 1),
290 (1, 2),
291 (2, 3),
292 (3, 4),
293 (4, 0),
294 (5, 7),
295 (7, 9),
296 (9, 6),
297 (6, 8),
298 (8, 5),
299 (0, 5),
300 (1, 6),
301 (2, 7),
302 (3, 8),
303 (4, 9),
304 ];
305 for (u, v) in edges {
306 g.add_edge(u, v).unwrap();
307 }
308 assert!(!is_series_parallel(&g).unwrap());
309 }
310
311 #[test]
312 fn k23_sp() {
313 let mut g = Graph::with_vertices(5);
317 g.add_edge(0, 2).unwrap();
318 g.add_edge(0, 3).unwrap();
319 g.add_edge(0, 4).unwrap();
320 g.add_edge(1, 2).unwrap();
321 g.add_edge(1, 3).unwrap();
322 g.add_edge(1, 4).unwrap();
323 assert!(is_series_parallel(&g).unwrap());
324 }
325
326 #[test]
327 fn two_triangles_shared_edge() {
328 let mut g = Graph::with_vertices(4);
331 g.add_edge(0, 1).unwrap();
332 g.add_edge(0, 2).unwrap();
333 g.add_edge(1, 2).unwrap();
334 g.add_edge(1, 3).unwrap();
335 g.add_edge(2, 3).unwrap();
336 assert!(is_series_parallel(&g).unwrap());
337 }
338
339 #[test]
340 fn directed_treated_as_undirected() {
341 let mut g = Graph::new(4, true).unwrap();
342 for i in 0..4u32 {
343 for j in (i + 1)..4 {
344 g.add_edge(i, j).unwrap();
345 }
346 }
347 assert!(!is_series_parallel(&g).unwrap());
349 }
350
351 #[test]
352 fn disconnected_sp() {
353 let mut g = Graph::with_vertices(6);
355 g.add_edge(0, 1).unwrap();
356 g.add_edge(1, 2).unwrap();
357 g.add_edge(2, 0).unwrap();
358 g.add_edge(3, 4).unwrap();
359 g.add_edge(4, 5).unwrap();
360 g.add_edge(5, 3).unwrap();
361 assert!(is_series_parallel(&g).unwrap());
362 }
363
364 #[test]
365 fn wheel_w4_not_sp() {
366 let mut g = Graph::with_vertices(5);
368 g.add_edge(0, 1).unwrap();
369 g.add_edge(0, 2).unwrap();
370 g.add_edge(0, 3).unwrap();
371 g.add_edge(0, 4).unwrap();
372 g.add_edge(1, 2).unwrap();
373 g.add_edge(2, 3).unwrap();
374 g.add_edge(3, 4).unwrap();
375 g.add_edge(4, 1).unwrap();
376 assert!(!is_series_parallel(&g).unwrap());
377 }
378}