rust_igraph/algorithms/properties/
subgraph_ratios.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::cast_sign_loss,
14 clippy::many_single_char_names,
15 clippy::needless_range_loop,
16 clippy::similar_names,
17 clippy::too_many_lines
18)]
19
20use crate::core::{Graph, IgraphResult};
21
22pub fn pendant_edge_ratio(graph: &Graph) -> IgraphResult<f64> {
38 let m = graph.ecount();
39 if m == 0 {
40 return Ok(0.0);
41 }
42
43 let n = graph.vcount() as usize;
44 let mut degrees = Vec::with_capacity(n);
45 for v in 0..n {
46 degrees.push(graph.degree(v as u32)?);
47 }
48
49 let mut pendant_count = 0_usize;
50 for (u, v) in graph.edges() {
51 if degrees[u as usize] == 1 || degrees[v as usize] == 1 {
52 pendant_count += 1;
53 }
54 }
55
56 Ok(pendant_count as f64 / m as f64)
57}
58
59pub fn bridge_ratio(graph: &Graph) -> IgraphResult<f64> {
75 let m = graph.ecount();
76 if m == 0 {
77 return Ok(0.0);
78 }
79
80 let bridge_count = count_bridges(graph)?;
81 Ok(bridge_count as f64 / m as f64)
82}
83
84pub fn triangle_participation(graph: &Graph) -> IgraphResult<f64> {
99 let n = graph.vcount() as usize;
100 if n < 3 {
101 return Ok(0.0);
102 }
103
104 let mut in_triangle = vec![false; n];
105
106 for v in 0..n {
107 if in_triangle[v] {
108 continue;
109 }
110 let vid = v as u32;
111 let neighbors = graph.neighbors(vid)?;
112 for i in 0..neighbors.len() {
113 for j in (i + 1)..neighbors.len() {
114 let a = neighbors[i];
115 let b = neighbors[j];
116 if graph.has_edge(a, b) {
117 in_triangle[v] = true;
118 in_triangle[a as usize] = true;
119 in_triangle[b as usize] = true;
120 }
121 }
122 }
123 }
124
125 let count = in_triangle.iter().filter(|&&x| x).count();
126 Ok(count as f64 / n as f64)
127}
128
129pub fn isolated_vertex_ratio(graph: &Graph) -> IgraphResult<f64> {
144 let n = graph.vcount() as usize;
145 if n == 0 {
146 return Ok(0.0);
147 }
148
149 let mut isolated = 0_usize;
150 for v in 0..n {
151 if graph.degree(v as u32)? == 0 {
152 isolated += 1;
153 }
154 }
155
156 Ok(isolated as f64 / n as f64)
157}
158
159fn count_bridges(graph: &Graph) -> IgraphResult<usize> {
160 let n = graph.vcount() as usize;
161 if n == 0 {
162 return Ok(0);
163 }
164
165 let mut disc = vec![0_u32; n];
166 let mut low = vec![0_u32; n];
167 let mut visited = vec![false; n];
168 let mut timer = 1_u32;
169 let mut bridge_count = 0_usize;
170
171 for start in 0..n {
172 if visited[start] {
173 continue;
174 }
175 let mut stack: Vec<(u32, i64, usize)> = vec![(start as u32, -1, 0)];
176 visited[start] = true;
177 disc[start] = timer;
178 low[start] = timer;
179 timer += 1;
180
181 while let Some(&mut (v, parent, ref mut ni)) = stack.last_mut() {
182 let neighbors = graph.neighbors(v)?;
183 if *ni < neighbors.len() {
184 let u = neighbors[*ni];
185 *ni += 1;
186 if i64::from(u) == parent {
187 continue;
188 }
189 let ui = u as usize;
190 if visited[ui] {
191 if disc[ui] < low[v as usize] {
192 low[v as usize] = disc[ui];
193 }
194 } else {
195 visited[ui] = true;
196 disc[ui] = timer;
197 low[ui] = timer;
198 timer += 1;
199 stack.push((u, i64::from(v), 0));
200 }
201 } else {
202 let vi = v as usize;
203 if parent >= 0 {
204 let pi = parent as usize;
205 if low[vi] < low[pi] {
206 low[pi] = low[vi];
207 }
208 if low[vi] > disc[pi] {
209 bridge_count += 1;
210 }
211 }
212 stack.pop();
213 }
214 }
215 }
216
217 Ok(bridge_count)
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 fn single_edge() -> Graph {
225 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
226 }
227
228 fn path3() -> Graph {
229 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
230 }
231
232 fn k3() -> Graph {
233 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
234 }
235
236 fn k4() -> Graph {
237 Graph::from_edges(
238 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
239 false,
240 Some(4),
241 )
242 .unwrap()
243 }
244
245 fn cycle4() -> Graph {
246 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
247 }
248
249 fn star5() -> Graph {
250 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
251 }
252
253 fn paw() -> Graph {
254 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
255 }
256
257 #[test]
260 fn per_empty() {
261 let g = Graph::with_vertices(0);
262 assert!(pendant_edge_ratio(&g).unwrap().abs() < 1e-10);
263 }
264
265 #[test]
266 fn per_isolated() {
267 let g = Graph::with_vertices(5);
268 assert!(pendant_edge_ratio(&g).unwrap().abs() < 1e-10);
269 }
270
271 #[test]
272 fn per_single_edge() {
273 assert!((pendant_edge_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
275 }
276
277 #[test]
278 fn per_path3() {
279 assert!((pendant_edge_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
281 }
282
283 #[test]
284 fn per_k3() {
285 assert!(pendant_edge_ratio(&k3()).unwrap().abs() < 1e-10);
287 }
288
289 #[test]
290 fn per_k4() {
291 assert!(pendant_edge_ratio(&k4()).unwrap().abs() < 1e-10);
293 }
294
295 #[test]
296 fn per_cycle4() {
297 assert!(pendant_edge_ratio(&cycle4()).unwrap().abs() < 1e-10);
299 }
300
301 #[test]
302 fn per_star5() {
303 assert!((pendant_edge_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
305 }
306
307 #[test]
308 fn per_paw() {
309 assert!((pendant_edge_ratio(&paw()).unwrap() - 0.25).abs() < 1e-10);
312 }
313
314 #[test]
317 fn br_empty() {
318 let g = Graph::with_vertices(0);
319 assert!(bridge_ratio(&g).unwrap().abs() < 1e-10);
320 }
321
322 #[test]
323 fn br_isolated() {
324 let g = Graph::with_vertices(5);
325 assert!(bridge_ratio(&g).unwrap().abs() < 1e-10);
326 }
327
328 #[test]
329 fn br_single_edge() {
330 assert!((bridge_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
332 }
333
334 #[test]
335 fn br_path3() {
336 assert!((bridge_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
338 }
339
340 #[test]
341 fn br_k3() {
342 assert!(bridge_ratio(&k3()).unwrap().abs() < 1e-10);
344 }
345
346 #[test]
347 fn br_k4() {
348 assert!(bridge_ratio(&k4()).unwrap().abs() < 1e-10);
350 }
351
352 #[test]
353 fn br_cycle4() {
354 assert!(bridge_ratio(&cycle4()).unwrap().abs() < 1e-10);
356 }
357
358 #[test]
359 fn br_star5() {
360 assert!((bridge_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
362 }
363
364 #[test]
365 fn br_paw() {
366 assert!((bridge_ratio(&paw()).unwrap() - 0.25).abs() < 1e-10);
368 }
369
370 #[test]
373 fn tp_empty() {
374 let g = Graph::with_vertices(0);
375 assert!(triangle_participation(&g).unwrap().abs() < 1e-10);
376 }
377
378 #[test]
379 fn tp_two() {
380 let g = Graph::with_vertices(2);
381 assert!(triangle_participation(&g).unwrap().abs() < 1e-10);
382 }
383
384 #[test]
385 fn tp_path3() {
386 assert!(triangle_participation(&path3()).unwrap().abs() < 1e-10);
388 }
389
390 #[test]
391 fn tp_k3() {
392 assert!((triangle_participation(&k3()).unwrap() - 1.0).abs() < 1e-10);
394 }
395
396 #[test]
397 fn tp_k4() {
398 assert!((triangle_participation(&k4()).unwrap() - 1.0).abs() < 1e-10);
400 }
401
402 #[test]
403 fn tp_cycle4() {
404 assert!(triangle_participation(&cycle4()).unwrap().abs() < 1e-10);
406 }
407
408 #[test]
409 fn tp_star5() {
410 assert!(triangle_participation(&star5()).unwrap().abs() < 1e-10);
412 }
413
414 #[test]
415 fn tp_paw() {
416 assert!((triangle_participation(&paw()).unwrap() - 0.75).abs() < 1e-10);
419 }
420
421 #[test]
422 fn tp_single_edge() {
423 assert!(triangle_participation(&single_edge()).unwrap().abs() < 1e-10);
425 }
426
427 #[test]
430 fn ivr_empty() {
431 let g = Graph::with_vertices(0);
432 assert!(isolated_vertex_ratio(&g).unwrap().abs() < 1e-10);
433 }
434
435 #[test]
436 fn ivr_all_isolated() {
437 let g = Graph::with_vertices(5);
438 assert!((isolated_vertex_ratio(&g).unwrap() - 1.0).abs() < 1e-10);
439 }
440
441 #[test]
442 fn ivr_single_edge() {
443 assert!(isolated_vertex_ratio(&single_edge()).unwrap().abs() < 1e-10);
445 }
446
447 #[test]
448 fn ivr_k3() {
449 assert!(isolated_vertex_ratio(&k3()).unwrap().abs() < 1e-10);
450 }
451
452 #[test]
453 fn ivr_with_isolates() {
454 let g = Graph::from_edges(&[(0, 1)], false, Some(5)).unwrap();
456 assert!((isolated_vertex_ratio(&g).unwrap() - 0.6).abs() < 1e-10);
457 }
458
459 #[test]
460 fn ivr_star5() {
461 assert!(isolated_vertex_ratio(&star5()).unwrap().abs() < 1e-10);
462 }
463
464 #[test]
465 fn ivr_paw() {
466 assert!(isolated_vertex_ratio(&paw()).unwrap().abs() < 1e-10);
467 }
468
469 #[test]
472 fn per_in_01() {
473 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
474 let r = pendant_edge_ratio(g).unwrap();
475 assert!(r >= -1e-10);
476 assert!(r <= 1.0 + 1e-10);
477 }
478 }
479
480 #[test]
481 fn br_in_01() {
482 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
483 let r = bridge_ratio(g).unwrap();
484 assert!(r >= -1e-10);
485 assert!(r <= 1.0 + 1e-10);
486 }
487 }
488
489 #[test]
490 fn tp_in_01() {
491 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
492 let r = triangle_participation(g).unwrap();
493 assert!(r >= -1e-10);
494 assert!(r <= 1.0 + 1e-10);
495 }
496 }
497
498 #[test]
499 fn ivr_in_01() {
500 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
501 let r = isolated_vertex_ratio(g).unwrap();
502 assert!(r >= -1e-10);
503 assert!(r <= 1.0 + 1e-10);
504 }
505 }
506
507 #[test]
508 fn trees_all_bridges() {
509 assert!((bridge_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
511 assert!((bridge_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
512 }
513
514 #[test]
515 fn pendant_implies_bridge_for_trees() {
516 let path_p = pendant_edge_ratio(&path3()).unwrap();
520 let path_b = bridge_ratio(&path3()).unwrap();
521 assert!((path_p - path_b).abs() < 1e-10);
522
523 let star_p = pendant_edge_ratio(&star5()).unwrap();
524 let star_b = bridge_ratio(&star5()).unwrap();
525 assert!((star_p - star_b).abs() < 1e-10);
526 }
527}