rust_igraph/algorithms/properties/
edge_density_ratios.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::many_single_char_names,
14 clippy::needless_range_loop,
15 clippy::similar_names,
16 clippy::too_many_lines
17)]
18
19use crate::core::{Graph, IgraphResult};
20
21pub fn self_loop_ratio(graph: &Graph) -> IgraphResult<f64> {
36 let m = graph.ecount();
37 if m == 0 {
38 return Ok(0.0);
39 }
40
41 let mut loop_count = 0_usize;
42 for (u, v) in graph.edges() {
43 if u == v {
44 loop_count += 1;
45 }
46 }
47
48 Ok(loop_count as f64 / m as f64)
49}
50
51pub fn multi_edge_ratio(graph: &Graph) -> IgraphResult<f64> {
68 let m = graph.ecount();
69 if m == 0 {
70 return Ok(0.0);
71 }
72
73 let directed = graph.is_directed();
74 let mut edge_set = std::collections::HashSet::new();
75 let mut multi_count = 0_usize;
76
77 for (u, v) in graph.edges() {
78 let key = if directed {
79 (u, v)
80 } else {
81 (u.min(v), u.max(v))
82 };
83 if !edge_set.insert(key) {
84 multi_count += 1;
85 }
86 }
87
88 Ok(multi_count as f64 / m as f64)
89}
90
91pub fn reciprocity_ratio(graph: &Graph) -> IgraphResult<f64> {
108 let m = graph.ecount();
109 if m == 0 {
110 return Ok(0.0);
111 }
112
113 if !graph.is_directed() {
114 return Ok(1.0);
115 }
116
117 let mut edge_set = std::collections::HashSet::new();
118 for (u, v) in graph.edges() {
119 edge_set.insert((u, v));
120 }
121
122 let mut reciprocal_count = 0_usize;
123 for &(u, v) in &edge_set {
124 if edge_set.contains(&(v, u)) {
125 reciprocal_count += 1;
126 }
127 }
128
129 Ok(reciprocal_count as f64 / m as f64)
130}
131
132pub fn avg_local_clustering(graph: &Graph) -> IgraphResult<f64> {
149 let n = graph.vcount() as usize;
150 if n == 0 {
151 return Ok(0.0);
152 }
153
154 let mut sum = 0.0_f64;
155 let mut count = 0_usize;
156
157 for v in 0..n {
158 let vid = v as u32;
159 let deg = graph.degree(vid)?;
160 if deg < 2 {
161 continue;
162 }
163
164 let neighbors = graph.neighbors(vid)?;
165 let mut triangles = 0_usize;
166 for i in 0..neighbors.len() {
167 for j in (i + 1)..neighbors.len() {
168 if graph.has_edge(neighbors[i], neighbors[j]) {
169 triangles += 1;
170 }
171 }
172 }
173
174 let pairs = deg * (deg - 1) / 2;
175 sum += triangles as f64 / pairs as f64;
176 count += 1;
177 }
178
179 if count == 0 {
180 return Ok(0.0);
181 }
182
183 Ok(sum / count as f64)
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 fn single_edge() -> Graph {
191 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
192 }
193
194 fn path3() -> Graph {
195 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
196 }
197
198 fn k3() -> Graph {
199 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
200 }
201
202 fn k4() -> Graph {
203 Graph::from_edges(
204 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
205 false,
206 Some(4),
207 )
208 .unwrap()
209 }
210
211 fn cycle4() -> Graph {
212 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
213 }
214
215 fn star5() -> Graph {
216 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
217 }
218
219 fn paw() -> Graph {
220 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
221 }
222
223 #[test]
226 fn slr_empty() {
227 let g = Graph::with_vertices(0);
228 assert!(self_loop_ratio(&g).unwrap().abs() < 1e-10);
229 }
230
231 #[test]
232 fn slr_isolated() {
233 let g = Graph::with_vertices(5);
234 assert!(self_loop_ratio(&g).unwrap().abs() < 1e-10);
235 }
236
237 #[test]
238 fn slr_single_edge() {
239 assert!(self_loop_ratio(&single_edge()).unwrap().abs() < 1e-10);
240 }
241
242 #[test]
243 fn slr_k3() {
244 assert!(self_loop_ratio(&k3()).unwrap().abs() < 1e-10);
245 }
246
247 #[test]
248 fn slr_k4() {
249 assert!(self_loop_ratio(&k4()).unwrap().abs() < 1e-10);
250 }
251
252 #[test]
253 fn slr_cycle4() {
254 assert!(self_loop_ratio(&cycle4()).unwrap().abs() < 1e-10);
255 }
256
257 #[test]
258 fn slr_star5() {
259 assert!(self_loop_ratio(&star5()).unwrap().abs() < 1e-10);
260 }
261
262 #[test]
263 fn slr_with_loops() {
264 let mut g = Graph::with_vertices(3);
266 g.add_edge(0, 1).unwrap();
267 g.add_edge(1, 2).unwrap();
268 g.add_edge(0, 0).unwrap();
269 assert!((self_loop_ratio(&g).unwrap() - 1.0 / 3.0).abs() < 1e-10);
270 }
271
272 #[test]
273 fn slr_all_loops() {
274 let mut g = Graph::with_vertices(2);
276 g.add_edge(0, 0).unwrap();
277 g.add_edge(1, 1).unwrap();
278 assert!((self_loop_ratio(&g).unwrap() - 1.0).abs() < 1e-10);
279 }
280
281 #[test]
284 fn mer_empty() {
285 let g = Graph::with_vertices(0);
286 assert!(multi_edge_ratio(&g).unwrap().abs() < 1e-10);
287 }
288
289 #[test]
290 fn mer_isolated() {
291 let g = Graph::with_vertices(5);
292 assert!(multi_edge_ratio(&g).unwrap().abs() < 1e-10);
293 }
294
295 #[test]
296 fn mer_single_edge() {
297 assert!(multi_edge_ratio(&single_edge()).unwrap().abs() < 1e-10);
298 }
299
300 #[test]
301 fn mer_k3() {
302 assert!(multi_edge_ratio(&k3()).unwrap().abs() < 1e-10);
303 }
304
305 #[test]
306 fn mer_k4() {
307 assert!(multi_edge_ratio(&k4()).unwrap().abs() < 1e-10);
308 }
309
310 #[test]
311 fn mer_cycle4() {
312 assert!(multi_edge_ratio(&cycle4()).unwrap().abs() < 1e-10);
313 }
314
315 #[test]
316 fn mer_with_multi() {
317 let mut g = Graph::with_vertices(3);
319 g.add_edge(0, 1).unwrap();
320 g.add_edge(0, 1).unwrap();
321 g.add_edge(1, 2).unwrap();
322 assert!((multi_edge_ratio(&g).unwrap() - 1.0 / 3.0).abs() < 1e-10);
323 }
324
325 #[test]
326 fn mer_all_multi() {
327 let mut g = Graph::with_vertices(2);
329 g.add_edge(0, 1).unwrap();
330 g.add_edge(0, 1).unwrap();
331 assert!((multi_edge_ratio(&g).unwrap() - 0.5).abs() < 1e-10);
332 }
333
334 #[test]
337 fn rr_empty() {
338 let g = Graph::with_vertices(0);
339 assert!(reciprocity_ratio(&g).unwrap().abs() < 1e-10);
340 }
341
342 #[test]
343 fn rr_isolated() {
344 let g = Graph::with_vertices(5);
345 assert!(reciprocity_ratio(&g).unwrap().abs() < 1e-10);
346 }
347
348 #[test]
349 fn rr_undirected_always_one() {
350 assert!((reciprocity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
352 assert!((reciprocity_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
353 }
354
355 #[test]
356 fn rr_directed_full_reciprocal() {
357 let g = Graph::from_edges(
359 &[(0, 1), (1, 0), (1, 2), (2, 1), (0, 2), (2, 0)],
360 true,
361 Some(3),
362 )
363 .unwrap();
364 assert!((reciprocity_ratio(&g).unwrap() - 1.0).abs() < 1e-10);
365 }
366
367 #[test]
368 fn rr_directed_no_reciprocal() {
369 let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], true, Some(3)).unwrap();
371 assert!(reciprocity_ratio(&g).unwrap().abs() < 1e-10);
372 }
373
374 #[test]
375 fn rr_directed_partial() {
376 let g = Graph::from_edges(&[(0, 1), (1, 0), (0, 2)], true, Some(3)).unwrap();
378 assert!((reciprocity_ratio(&g).unwrap() - 2.0 / 3.0).abs() < 1e-10);
379 }
380
381 #[test]
384 fn alc_empty() {
385 let g = Graph::with_vertices(0);
386 assert!(avg_local_clustering(&g).unwrap().abs() < 1e-10);
387 }
388
389 #[test]
390 fn alc_isolated() {
391 let g = Graph::with_vertices(5);
392 assert!(avg_local_clustering(&g).unwrap().abs() < 1e-10);
393 }
394
395 #[test]
396 fn alc_single_edge() {
397 assert!(avg_local_clustering(&single_edge()).unwrap().abs() < 1e-10);
399 }
400
401 #[test]
402 fn alc_path3() {
403 assert!(avg_local_clustering(&path3()).unwrap().abs() < 1e-10);
406 }
407
408 #[test]
409 fn alc_k3() {
410 assert!((avg_local_clustering(&k3()).unwrap() - 1.0).abs() < 1e-10);
412 }
413
414 #[test]
415 fn alc_k4() {
416 assert!((avg_local_clustering(&k4()).unwrap() - 1.0).abs() < 1e-10);
418 }
419
420 #[test]
421 fn alc_cycle4() {
422 assert!(avg_local_clustering(&cycle4()).unwrap().abs() < 1e-10);
424 }
425
426 #[test]
427 fn alc_star5() {
428 assert!(avg_local_clustering(&star5()).unwrap().abs() < 1e-10);
432 }
433
434 #[test]
435 fn alc_paw() {
436 assert!((avg_local_clustering(&paw()).unwrap() - 7.0 / 9.0).abs() < 1e-10);
443 }
444
445 #[test]
448 fn slr_in_01() {
449 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
450 let r = self_loop_ratio(g).unwrap();
451 assert!(r >= -1e-10);
452 assert!(r <= 1.0 + 1e-10);
453 }
454 }
455
456 #[test]
457 fn mer_in_01() {
458 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
459 let r = multi_edge_ratio(g).unwrap();
460 assert!(r >= -1e-10);
461 assert!(r <= 1.0 + 1e-10);
462 }
463 }
464
465 #[test]
466 fn rr_in_01() {
467 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
468 let r = reciprocity_ratio(g).unwrap();
469 assert!(r >= -1e-10);
470 assert!(r <= 1.0 + 1e-10);
471 }
472 }
473
474 #[test]
475 fn alc_in_01() {
476 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
477 let r = avg_local_clustering(g).unwrap();
478 assert!(r >= -1e-10);
479 assert!(r <= 1.0 + 1e-10);
480 }
481 }
482
483 #[test]
484 fn simple_graphs_no_loops_no_multi() {
485 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
486 assert!(self_loop_ratio(g).unwrap().abs() < 1e-10);
487 assert!(multi_edge_ratio(g).unwrap().abs() < 1e-10);
488 }
489 }
490
491 #[test]
492 fn complete_graphs_full_clustering() {
493 assert!((avg_local_clustering(&k3()).unwrap() - 1.0).abs() < 1e-10);
494 assert!((avg_local_clustering(&k4()).unwrap() - 1.0).abs() < 1e-10);
495 }
496}