rust_igraph/algorithms/properties/
graph_density_profile.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
22pub fn triangle_density(graph: &Graph) -> IgraphResult<f64> {
41 let n = u64::from(graph.vcount());
42 if n < 3 {
43 return Ok(0.0);
44 }
45
46 let mut tri_count = 0_u64;
47 let nv = n as usize;
48 for v in 0..nv {
49 let vid = v as u32;
50 let neighbors = graph.neighbors(vid)?;
51 for i in 0..neighbors.len() {
52 for j in (i + 1)..neighbors.len() {
53 let a = neighbors[i];
54 let b = neighbors[j];
55 if a > vid && b > vid && graph.has_edge(a, b) {
56 tri_count += 1;
57 }
58 }
59 }
60 }
61
62 let denom = n * (n - 1) * (n - 2);
63 Ok(6.0 * tri_count as f64 / denom as f64)
64}
65
66pub fn square_density(graph: &Graph) -> IgraphResult<f64> {
85 let n = u64::from(graph.vcount());
86 if n < 4 {
87 return Ok(0.0);
88 }
89
90 let nv = n as usize;
91 let mut count = 0_u64;
92
93 for u in 0..nv {
94 let uid = u as u32;
95 for v in (u + 1)..nv {
96 let vid = v as u32;
97 if graph.has_edge(uid, vid) {
98 continue;
99 }
100 let nu = graph.neighbors(uid)?;
101 let nv_list = graph.neighbors(vid)?;
102
103 let mut common = Vec::new();
104 for &w in &nu {
105 if w != vid {
106 for &x in &nv_list {
107 if x == w && x != uid {
108 common.push(w);
109 break;
110 }
111 }
112 }
113 }
114
115 for i in 0..common.len() {
116 for j in (i + 1)..common.len() {
117 if !graph.has_edge(common[i], common[j]) {
118 count += 1;
119 }
120 }
121 }
122 }
123 }
124
125 let denom = n * (n - 1) * (n - 2) * (n - 3) / 24;
126 if denom == 0 {
127 return Ok(0.0);
128 }
129
130 Ok(count as f64 / (2.0 * denom as f64))
132}
133
134pub fn edge_connectivity_ratio(graph: &Graph) -> IgraphResult<f64> {
152 let n = u64::from(graph.vcount());
153 if n < 2 {
154 return Ok(0.0);
155 }
156
157 let m = graph.ecount() as f64;
158 let denom = n * (n - 1);
159
160 if graph.is_directed() {
161 Ok(m / denom as f64)
162 } else {
163 Ok(2.0 * m / denom as f64)
164 }
165}
166
167pub fn degree_density(graph: &Graph) -> IgraphResult<f64> {
187 let n = graph.vcount() as usize;
188 if n < 2 {
189 return Ok(0.0);
190 }
191
192 let mut sum_d = 0.0_f64;
193 let mut sum_d2 = 0.0_f64;
194
195 for v in 0..n {
196 let d = graph.degree(v as u32)? as f64;
197 sum_d += d;
198 sum_d2 += d * d;
199 }
200
201 if sum_d < 1e-15 {
202 return Ok(0.0);
203 }
204
205 let mean_d = sum_d / n as f64;
206 let mean_d2 = sum_d2 / n as f64;
207
208 Ok((mean_d2 / mean_d - 1.0) / (n as f64 - 1.0))
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 fn single_edge() -> Graph {
216 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
217 }
218
219 fn path3() -> Graph {
220 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
221 }
222
223 fn k3() -> Graph {
224 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
225 }
226
227 fn k4() -> Graph {
228 Graph::from_edges(
229 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
230 false,
231 Some(4),
232 )
233 .unwrap()
234 }
235
236 fn cycle4() -> Graph {
237 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
238 }
239
240 fn star5() -> Graph {
241 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
242 }
243
244 fn paw() -> Graph {
245 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
246 }
247
248 #[test]
251 fn tri_empty() {
252 let g = Graph::with_vertices(0);
253 assert!(triangle_density(&g).unwrap().abs() < 1e-10);
254 }
255
256 #[test]
257 fn tri_two() {
258 let g = Graph::with_vertices(2);
259 assert!(triangle_density(&g).unwrap().abs() < 1e-10);
260 }
261
262 #[test]
263 fn tri_k3() {
264 assert!((triangle_density(&k3()).unwrap() - 1.0).abs() < 1e-10);
266 }
267
268 #[test]
269 fn tri_k4() {
270 assert!((triangle_density(&k4()).unwrap() - 1.0).abs() < 1e-10);
272 }
273
274 #[test]
275 fn tri_path3() {
276 assert!(triangle_density(&path3()).unwrap().abs() < 1e-10);
278 }
279
280 #[test]
281 fn tri_cycle4() {
282 assert!(triangle_density(&cycle4()).unwrap().abs() < 1e-10);
284 }
285
286 #[test]
287 fn tri_star5() {
288 assert!(triangle_density(&star5()).unwrap().abs() < 1e-10);
290 }
291
292 #[test]
293 fn tri_paw() {
294 assert!((triangle_density(&paw()).unwrap() - 0.25).abs() < 1e-10);
297 }
298
299 #[test]
302 fn sq_empty() {
303 let g = Graph::with_vertices(0);
304 assert!(square_density(&g).unwrap().abs() < 1e-10);
305 }
306
307 #[test]
308 fn sq_three() {
309 assert!(square_density(&k3()).unwrap().abs() < 1e-10);
310 }
311
312 #[test]
313 fn sq_cycle4() {
314 assert!((square_density(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
317 }
318
319 #[test]
320 fn sq_k4() {
321 assert!(square_density(&k4()).unwrap().abs() < 1e-10);
323 }
324
325 #[test]
326 fn sq_star5() {
327 assert!(square_density(&star5()).unwrap().abs() < 1e-10);
329 }
330
331 #[test]
332 fn sq_path3() {
333 assert!(square_density(&path3()).unwrap().abs() < 1e-10);
334 }
335
336 #[test]
339 fn ecr_empty() {
340 let g = Graph::with_vertices(0);
341 assert!(edge_connectivity_ratio(&g).unwrap().abs() < 1e-10);
342 }
343
344 #[test]
345 fn ecr_single() {
346 let g = Graph::with_vertices(1);
347 assert!(edge_connectivity_ratio(&g).unwrap().abs() < 1e-10);
348 }
349
350 #[test]
351 fn ecr_k3() {
352 assert!((edge_connectivity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
354 }
355
356 #[test]
357 fn ecr_k4() {
358 assert!((edge_connectivity_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
360 }
361
362 #[test]
363 fn ecr_cycle4() {
364 assert!((edge_connectivity_ratio(&cycle4()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
366 }
367
368 #[test]
369 fn ecr_single_edge() {
370 assert!((edge_connectivity_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
372 }
373
374 #[test]
375 fn ecr_star5() {
376 assert!((edge_connectivity_ratio(&star5()).unwrap() - 0.4).abs() < 1e-10);
378 }
379
380 #[test]
381 fn ecr_path3() {
382 assert!((edge_connectivity_ratio(&path3()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
384 }
385
386 #[test]
387 fn ecr_paw() {
388 assert!((edge_connectivity_ratio(&paw()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
390 }
391
392 #[test]
395 fn dd_empty() {
396 let g = Graph::with_vertices(0);
397 assert!(degree_density(&g).unwrap().abs() < 1e-10);
398 }
399
400 #[test]
401 fn dd_single() {
402 let g = Graph::with_vertices(1);
403 assert!(degree_density(&g).unwrap().abs() < 1e-10);
404 }
405
406 #[test]
407 fn dd_isolated() {
408 let g = Graph::with_vertices(5);
409 assert!(degree_density(&g).unwrap().abs() < 1e-10);
410 }
411
412 #[test]
413 fn dd_k3() {
414 assert!((degree_density(&k3()).unwrap() - 0.5).abs() < 1e-10);
416 }
417
418 #[test]
419 fn dd_k4() {
420 assert!((degree_density(&k4()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
422 }
423
424 #[test]
425 fn dd_single_edge() {
426 assert!(degree_density(&single_edge()).unwrap().abs() < 1e-10);
428 }
429
430 #[test]
431 fn dd_star5() {
432 assert!((degree_density(&star5()).unwrap() - 0.375).abs() < 1e-10);
437 }
438
439 #[test]
440 fn dd_path3() {
441 assert!((degree_density(&path3()).unwrap() - 0.25).abs() < 1e-10);
445 }
446
447 #[test]
448 fn dd_paw() {
449 let expected = 1.25 / 3.0;
453 assert!((degree_density(&paw()).unwrap() - expected).abs() < 1e-10);
454 }
455
456 #[test]
459 fn tri_in_01() {
460 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
461 let t = triangle_density(g).unwrap();
462 assert!(t >= -1e-10);
463 assert!(t <= 1.0 + 1e-10);
464 }
465 }
466
467 #[test]
468 fn ecr_in_01() {
469 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
470 let r = edge_connectivity_ratio(g).unwrap();
471 assert!(r >= -1e-10);
472 assert!(r <= 1.0 + 1e-10);
473 }
474 }
475
476 #[test]
477 fn dd_nonneg() {
478 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
479 assert!(degree_density(g).unwrap() >= -1e-10);
480 }
481 }
482
483 #[test]
484 fn complete_ecr_is_one() {
485 assert!((edge_connectivity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
486 assert!((edge_connectivity_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
487 }
488
489 #[test]
490 fn complete_tri_is_one() {
491 assert!((triangle_density(&k3()).unwrap() - 1.0).abs() < 1e-10);
492 assert!((triangle_density(&k4()).unwrap() - 1.0).abs() < 1e-10);
493 }
494}