rust_igraph/algorithms/properties/
edge_degree_correlation.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 edge_degree_covariance(graph: &Graph) -> IgraphResult<f64> {
40 let mut m = 0_u64;
41 let mut sum_u = 0.0_f64;
42 let mut sum_v = 0.0_f64;
43 let mut sum_uv = 0.0_f64;
44
45 for (u, v) in graph.edges() {
46 if u == v {
47 continue;
48 }
49 let du = graph.degree(u)? as f64;
50 let dv = graph.degree(v)? as f64;
51 sum_u += du;
52 sum_v += dv;
53 sum_uv += du * dv;
54 m += 1;
55 }
56
57 if m == 0 {
58 return Ok(0.0);
59 }
60
61 let mf = m as f64;
62 let mean_u = sum_u / mf;
63 let mean_v = sum_v / mf;
64 let cov = sum_uv / mf - mean_u * mean_v;
65
66 Ok(cov)
67}
68
69pub fn edge_degree_pearson(graph: &Graph) -> IgraphResult<f64> {
88 let mut m = 0_u64;
89 let mut sum_u = 0.0_f64;
90 let mut sum_v = 0.0_f64;
91 let mut sum_uu = 0.0_f64;
92 let mut sum_vv = 0.0_f64;
93 let mut sum_uv = 0.0_f64;
94
95 for (u, v) in graph.edges() {
96 if u == v {
97 continue;
98 }
99 let du = graph.degree(u)? as f64;
100 let dv = graph.degree(v)? as f64;
101 sum_u += du;
102 sum_v += dv;
103 sum_uu += du * du;
104 sum_vv += dv * dv;
105 sum_uv += du * dv;
106 m += 1;
107 }
108
109 if m == 0 {
110 return Ok(0.0);
111 }
112
113 let mf = m as f64;
114 let var_u = sum_uu / mf - (sum_u / mf) * (sum_u / mf);
115 let var_v = sum_vv / mf - (sum_v / mf) * (sum_v / mf);
116 let cov = sum_uv / mf - (sum_u / mf) * (sum_v / mf);
117
118 let denom = (var_u * var_v).sqrt();
119 if denom < 1e-15 {
120 return Ok(0.0);
121 }
122
123 Ok(cov / denom)
124}
125
126pub fn edge_degree_cosine(graph: &Graph) -> IgraphResult<f64> {
144 let mut sum_uv = 0.0_f64;
145 let mut sum_uu = 0.0_f64;
146 let mut sum_vv = 0.0_f64;
147
148 for (u, v) in graph.edges() {
149 if u == v {
150 continue;
151 }
152 let du = graph.degree(u)? as f64;
153 let dv = graph.degree(v)? as f64;
154 sum_uv += du * dv;
155 sum_uu += du * du;
156 sum_vv += dv * dv;
157 }
158
159 let denom = (sum_uu * sum_vv).sqrt();
160 if denom < 1e-15 {
161 return Ok(0.0);
162 }
163
164 Ok(sum_uv / denom)
165}
166
167pub fn edge_degree_discrepancy(graph: &Graph) -> IgraphResult<f64> {
185 let mut m = 0_u64;
186 let mut sum_sq = 0.0_f64;
187
188 for (u, v) in graph.edges() {
189 if u == v {
190 continue;
191 }
192 let du = graph.degree(u)? as f64;
193 let dv = graph.degree(v)? as f64;
194 let diff = du - dv;
195 sum_sq += diff * diff;
196 m += 1;
197 }
198
199 if m == 0 {
200 return Ok(0.0);
201 }
202
203 Ok(sum_sq / (4.0 * m as f64))
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 fn single_edge() -> Graph {
211 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
212 }
213
214 fn path3() -> Graph {
215 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
216 }
217
218 fn k3() -> Graph {
219 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
220 }
221
222 fn k4() -> Graph {
223 Graph::from_edges(
224 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
225 false,
226 Some(4),
227 )
228 .unwrap()
229 }
230
231 fn cycle4() -> Graph {
232 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
233 }
234
235 fn star5() -> Graph {
236 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
237 }
238
239 fn paw() -> Graph {
240 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
241 }
242
243 #[test]
246 fn cov_empty() {
247 let g = Graph::with_vertices(0);
248 assert!(edge_degree_covariance(&g).unwrap().abs() < 1e-10);
249 }
250
251 #[test]
252 fn cov_isolated() {
253 let g = Graph::with_vertices(5);
254 assert!(edge_degree_covariance(&g).unwrap().abs() < 1e-10);
255 }
256
257 #[test]
258 fn cov_regular() {
259 assert!(edge_degree_covariance(&k3()).unwrap().abs() < 1e-10);
261 assert!(edge_degree_covariance(&k4()).unwrap().abs() < 1e-10);
262 assert!(edge_degree_covariance(&cycle4()).unwrap().abs() < 1e-10);
263 }
264
265 #[test]
266 fn cov_single_edge() {
267 assert!(edge_degree_covariance(&single_edge()).unwrap().abs() < 1e-10);
269 }
270
271 #[test]
272 fn cov_star5() {
273 assert!(edge_degree_covariance(&star5()).unwrap().abs() < 1e-10);
276 }
277
278 #[test]
279 fn cov_path3() {
280 assert!((edge_degree_covariance(&path3()).unwrap() - (-0.25)).abs() < 1e-10);
287 }
288
289 #[test]
290 fn cov_paw() {
291 assert!((edge_degree_covariance(&paw()).unwrap() - (-0.3125)).abs() < 1e-10);
298 }
299
300 #[test]
303 fn pearson_empty() {
304 let g = Graph::with_vertices(0);
305 assert!(edge_degree_pearson(&g).unwrap().abs() < 1e-10);
306 }
307
308 #[test]
309 fn pearson_regular() {
310 assert!(edge_degree_pearson(&k3()).unwrap().abs() < 1e-10);
312 assert!(edge_degree_pearson(&k4()).unwrap().abs() < 1e-10);
313 assert!(edge_degree_pearson(&cycle4()).unwrap().abs() < 1e-10);
314 }
315
316 #[test]
317 fn pearson_single_edge() {
318 assert!(edge_degree_pearson(&single_edge()).unwrap().abs() < 1e-10);
320 }
321
322 #[test]
323 fn pearson_star5() {
324 assert!(edge_degree_pearson(&star5()).unwrap().abs() < 1e-10);
326 }
327
328 #[test]
329 fn pearson_path3() {
330 assert!((edge_degree_pearson(&path3()).unwrap() - (-1.0)).abs() < 1e-10);
333 }
334
335 #[test]
336 fn pearson_in_range() {
337 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
338 let r = edge_degree_pearson(g).unwrap();
339 assert!(r >= -1.0 - 1e-10);
340 assert!(r <= 1.0 + 1e-10);
341 }
342 }
343
344 #[test]
347 fn cos_empty() {
348 let g = Graph::with_vertices(0);
349 assert!(edge_degree_cosine(&g).unwrap().abs() < 1e-10);
350 }
351
352 #[test]
353 fn cos_regular() {
354 assert!((edge_degree_cosine(&k3()).unwrap() - 1.0).abs() < 1e-10);
356 assert!((edge_degree_cosine(&k4()).unwrap() - 1.0).abs() < 1e-10);
357 assert!((edge_degree_cosine(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
358 }
359
360 #[test]
361 fn cos_single_edge() {
362 assert!((edge_degree_cosine(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
364 }
365
366 #[test]
367 fn cos_star5() {
368 assert!((edge_degree_cosine(&star5()).unwrap() - 1.0).abs() < 1e-10);
372 }
373
374 #[test]
375 fn cos_path3() {
376 assert!((edge_degree_cosine(&path3()).unwrap() - 0.8).abs() < 1e-10);
380 }
381
382 #[test]
383 fn cos_paw() {
384 let expected = 19.0 / (21.0_f64 * 23.0).sqrt();
388 assert!((edge_degree_cosine(&paw()).unwrap() - expected).abs() < 1e-10);
389 }
390
391 #[test]
392 fn cos_in_01() {
393 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
394 let c = edge_degree_cosine(g).unwrap();
395 assert!(c >= -1e-10);
396 assert!(c <= 1.0 + 1e-10);
397 }
398 }
399
400 #[test]
403 fn disc_empty() {
404 let g = Graph::with_vertices(0);
405 assert!(edge_degree_discrepancy(&g).unwrap().abs() < 1e-10);
406 }
407
408 #[test]
409 fn disc_regular() {
410 assert!(edge_degree_discrepancy(&k3()).unwrap().abs() < 1e-10);
412 assert!(edge_degree_discrepancy(&k4()).unwrap().abs() < 1e-10);
413 assert!(edge_degree_discrepancy(&cycle4()).unwrap().abs() < 1e-10);
414 }
415
416 #[test]
417 fn disc_single_edge() {
418 assert!(edge_degree_discrepancy(&single_edge()).unwrap().abs() < 1e-10);
420 }
421
422 #[test]
423 fn disc_star5() {
424 assert!((edge_degree_discrepancy(&star5()).unwrap() - 2.25).abs() < 1e-10);
427 }
428
429 #[test]
430 fn disc_path3() {
431 assert!((edge_degree_discrepancy(&path3()).unwrap() - 0.25).abs() < 1e-10);
434 }
435
436 #[test]
437 fn disc_paw() {
438 assert!((edge_degree_discrepancy(&paw()).unwrap() - 3.0 / 8.0).abs() < 1e-10);
441 }
442
443 #[test]
446 fn disc_nonnegative() {
447 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
448 assert!(edge_degree_discrepancy(g).unwrap() >= -1e-10);
449 }
450 }
451
452 #[test]
453 fn cov_zero_implies_disc_related() {
454 for g in &[k3(), k4(), cycle4()] {
456 assert!(edge_degree_covariance(g).unwrap().abs() < 1e-10);
457 assert!(edge_degree_discrepancy(g).unwrap().abs() < 1e-10);
458 }
459 }
460
461 #[test]
462 fn cos_ge_pearson_squared() {
463 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
465 assert!(edge_degree_cosine(g).unwrap() >= -1e-10);
466 }
467 }
468}