rust_igraph/algorithms/properties/
edge_neighborhood_overlap.rs1#![allow(
12 clippy::cast_possible_truncation,
13 clippy::cast_precision_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
22fn common_neighbors(graph: &Graph, u: u32, v: u32) -> IgraphResult<Vec<u32>> {
23 let nu = graph.neighbors(u)?;
24 let nv = graph.neighbors(v)?;
25 let mut nu_sorted = nu;
26 let mut nv_sorted = nv;
27 nu_sorted.sort_unstable();
28 nv_sorted.sort_unstable();
29
30 let mut common = Vec::new();
31 let (mut i, mut j) = (0, 0);
32 while i < nu_sorted.len() && j < nv_sorted.len() {
33 match nu_sorted[i].cmp(&nv_sorted[j]) {
34 std::cmp::Ordering::Equal => {
35 let w = nu_sorted[i];
36 if w != u && w != v {
37 common.push(w);
38 }
39 i += 1;
40 j += 1;
41 }
42 std::cmp::Ordering::Less => i += 1,
43 std::cmp::Ordering::Greater => j += 1,
44 }
45 }
46 Ok(common)
47}
48
49pub fn edge_common_neighbor_sum(graph: &Graph) -> IgraphResult<u64> {
66 let mut result = 0_u64;
67
68 for (u, v) in graph.edges() {
69 if u == v {
70 continue;
71 }
72 let cn = common_neighbors(graph, u, v)?;
73 result = result.saturating_add(cn.len() as u64);
74 }
75
76 Ok(result)
77}
78
79pub fn edge_jaccard_sum(graph: &Graph) -> IgraphResult<f64> {
97 let mut result = 0.0_f64;
98
99 for (u, v) in graph.edges() {
100 if u == v {
101 continue;
102 }
103 let nu = graph.neighbors(u)?;
104 let nv = graph.neighbors(v)?;
105
106 let mut nu_set: Vec<u32> = nu.into_iter().filter(|&w| w != u && w != v).collect();
107 let mut nv_set: Vec<u32> = nv.into_iter().filter(|&w| w != u && w != v).collect();
108 nu_set.sort_unstable();
109 nv_set.sort_unstable();
110
111 let mut inter = 0_usize;
112 let (mut i, mut j) = (0, 0);
113 while i < nu_set.len() && j < nv_set.len() {
114 match nu_set[i].cmp(&nv_set[j]) {
115 std::cmp::Ordering::Equal => {
116 inter += 1;
117 i += 1;
118 j += 1;
119 }
120 std::cmp::Ordering::Less => i += 1,
121 std::cmp::Ordering::Greater => j += 1,
122 }
123 }
124
125 let union_size = nu_set.len() + nv_set.len() - inter;
126 if union_size > 0 {
127 result += inter as f64 / union_size as f64;
128 }
129 }
130
131 Ok(result)
132}
133
134pub fn edge_overlap_sum(graph: &Graph) -> IgraphResult<f64> {
152 let mut result = 0.0_f64;
153
154 for (u, v) in graph.edges() {
155 if u == v {
156 continue;
157 }
158 let cn = common_neighbors(graph, u, v)?;
159 let du = graph.degree(u)?;
160 let dv = graph.degree(v)?;
161 let du_excl = du.saturating_sub(1);
162 let dv_excl = dv.saturating_sub(1);
163 let min_d = du_excl.min(dv_excl);
164 if min_d == 0 {
165 continue;
166 }
167 result += cn.len() as f64 / min_d as f64;
168 }
169
170 Ok(result)
171}
172
173pub fn edge_adamic_adar_sum(graph: &Graph) -> IgraphResult<f64> {
195 let mut result = 0.0_f64;
196
197 for (u, v) in graph.edges() {
198 if u == v {
199 continue;
200 }
201 let cn = common_neighbors(graph, u, v)?;
202 for &w in &cn {
203 let dw = graph.degree(w)?;
204 if dw >= 2 {
205 result += 1.0 / (dw as f64).ln();
206 }
207 }
208 }
209
210 Ok(result)
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 fn single_edge() -> Graph {
218 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
219 }
220
221 fn path3() -> Graph {
222 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
223 }
224
225 fn k3() -> Graph {
226 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
227 }
228
229 fn k4() -> Graph {
230 Graph::from_edges(
231 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
232 false,
233 Some(4),
234 )
235 .unwrap()
236 }
237
238 fn cycle4() -> Graph {
239 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
240 }
241
242 fn star5() -> Graph {
243 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
244 }
245
246 fn paw() -> Graph {
247 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
248 }
249
250 #[test]
253 fn cn_empty() {
254 let g = Graph::with_vertices(0);
255 assert_eq!(edge_common_neighbor_sum(&g).unwrap(), 0);
256 }
257
258 #[test]
259 fn cn_isolated() {
260 let g = Graph::with_vertices(5);
261 assert_eq!(edge_common_neighbor_sum(&g).unwrap(), 0);
262 }
263
264 #[test]
265 fn cn_single_edge() {
266 assert_eq!(edge_common_neighbor_sum(&single_edge()).unwrap(), 0);
268 }
269
270 #[test]
271 fn cn_path3() {
272 assert_eq!(edge_common_neighbor_sum(&path3()).unwrap(), 0);
274 }
275
276 #[test]
277 fn cn_k3() {
278 assert_eq!(edge_common_neighbor_sum(&k3()).unwrap(), 3);
280 }
281
282 #[test]
283 fn cn_k4() {
284 assert_eq!(edge_common_neighbor_sum(&k4()).unwrap(), 12);
286 }
287
288 #[test]
289 fn cn_star5() {
290 assert_eq!(edge_common_neighbor_sum(&star5()).unwrap(), 0);
292 }
293
294 #[test]
295 fn cn_cycle4() {
296 assert_eq!(edge_common_neighbor_sum(&cycle4()).unwrap(), 0);
300 }
301
302 #[test]
303 fn cn_paw() {
304 assert_eq!(edge_common_neighbor_sum(&paw()).unwrap(), 3);
309 }
310
311 #[test]
314 fn jac_empty() {
315 let g = Graph::with_vertices(0);
316 assert!(edge_jaccard_sum(&g).unwrap().abs() < 1e-10);
317 }
318
319 #[test]
320 fn jac_single_edge() {
321 assert!(edge_jaccard_sum(&single_edge()).unwrap().abs() < 1e-10);
323 }
324
325 #[test]
326 fn jac_path3() {
327 assert!(edge_jaccard_sum(&path3()).unwrap().abs() < 1e-10);
330 }
331
332 #[test]
333 fn jac_k3() {
334 assert!((edge_jaccard_sum(&k3()).unwrap() - 3.0).abs() < 1e-10);
337 }
338
339 #[test]
340 fn jac_k4() {
341 assert!((edge_jaccard_sum(&k4()).unwrap() - 6.0).abs() < 1e-10);
344 }
345
346 #[test]
347 fn jac_star5() {
348 assert!(edge_jaccard_sum(&star5()).unwrap().abs() < 1e-10);
350 }
351
352 #[test]
353 fn jac_paw() {
354 let expected = 1.0 + 0.5 + 0.5 + 0.0;
359 assert!((edge_jaccard_sum(&paw()).unwrap() - expected).abs() < 1e-10);
360 }
361
362 #[test]
365 fn ovl_empty() {
366 let g = Graph::with_vertices(0);
367 assert!(edge_overlap_sum(&g).unwrap().abs() < 1e-10);
368 }
369
370 #[test]
371 fn ovl_single_edge() {
372 assert!(edge_overlap_sum(&single_edge()).unwrap().abs() < 1e-10);
374 }
375
376 #[test]
377 fn ovl_path3() {
378 assert!(edge_overlap_sum(&path3()).unwrap().abs() < 1e-10);
381 }
382
383 #[test]
384 fn ovl_k3() {
385 assert!((edge_overlap_sum(&k3()).unwrap() - 3.0).abs() < 1e-10);
388 }
389
390 #[test]
391 fn ovl_k4() {
392 assert!((edge_overlap_sum(&k4()).unwrap() - 6.0).abs() < 1e-10);
395 }
396
397 #[test]
398 fn ovl_star5() {
399 assert!(edge_overlap_sum(&star5()).unwrap().abs() < 1e-10);
401 }
402
403 #[test]
404 fn ovl_paw() {
405 assert!((edge_overlap_sum(&paw()).unwrap() - 3.0).abs() < 1e-10);
410 }
411
412 #[test]
415 fn aa_empty() {
416 let g = Graph::with_vertices(0);
417 assert!(edge_adamic_adar_sum(&g).unwrap().abs() < 1e-10);
418 }
419
420 #[test]
421 fn aa_single_edge() {
422 assert!(edge_adamic_adar_sum(&single_edge()).unwrap().abs() < 1e-10);
423 }
424
425 #[test]
426 fn aa_path3() {
427 assert!(edge_adamic_adar_sum(&path3()).unwrap().abs() < 1e-10);
428 }
429
430 #[test]
431 fn aa_k3() {
432 let expected = 3.0 / 2.0_f64.ln();
435 assert!((edge_adamic_adar_sum(&k3()).unwrap() - expected).abs() < 1e-10);
436 }
437
438 #[test]
439 fn aa_k4() {
440 let expected = 12.0 / 3.0_f64.ln();
443 assert!((edge_adamic_adar_sum(&k4()).unwrap() - expected).abs() < 1e-10);
444 }
445
446 #[test]
447 fn aa_star5() {
448 assert!(edge_adamic_adar_sum(&star5()).unwrap().abs() < 1e-10);
449 }
450
451 #[test]
452 fn aa_paw() {
453 let expected = 1.0 / 3.0_f64.ln() + 2.0 / 2.0_f64.ln();
458 assert!((edge_adamic_adar_sum(&paw()).unwrap() - expected).abs() < 1e-10);
459 }
460
461 #[test]
464 fn cn_eq_3_times_triangles_for_complete() {
465 assert_eq!(edge_common_neighbor_sum(&k3()).unwrap(), 3);
469 assert_eq!(edge_common_neighbor_sum(&k4()).unwrap(), 12);
470 }
471
472 #[test]
473 fn jaccard_perfect_for_complete() {
474 assert!((edge_jaccard_sum(&k3()).unwrap() - 3.0).abs() < 1e-10);
476 assert!((edge_jaccard_sum(&k4()).unwrap() - 6.0).abs() < 1e-10);
477 }
478
479 #[test]
480 fn overlap_perfect_for_complete() {
481 assert!((edge_overlap_sum(&k3()).unwrap() - 3.0).abs() < 1e-10);
483 assert!((edge_overlap_sum(&k4()).unwrap() - 6.0).abs() < 1e-10);
484 }
485
486 #[test]
487 fn cn_bounded_by_m_times_n() {
488 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
489 let cn = edge_common_neighbor_sum(g).unwrap();
490 let bound = g.ecount() as u64 * u64::from(g.vcount());
491 assert!(cn <= bound);
492 }
493 }
494
495 #[test]
496 fn jaccard_in_0_m() {
497 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
498 let j = edge_jaccard_sum(g).unwrap();
499 let m = g.ecount() as f64;
500 assert!(j >= -1e-10);
501 assert!(j <= m + 1e-10);
502 }
503 }
504
505 #[test]
506 fn aa_nonnegative() {
507 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
508 assert!(edge_adamic_adar_sum(g).unwrap() >= -1e-10);
509 }
510 }
511}