rust_igraph/algorithms/properties/
distance_distribution_ratios.rs1#![allow(
11 clippy::cast_lossless,
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 distance_skewness(graph: &Graph) -> IgraphResult<f64> {
41 let moments = distance_moments(graph)?;
42 match moments {
43 None => Ok(0.0),
44 Some((_, variance, skew, _)) => {
45 if variance < 1e-30 {
46 return Ok(0.0);
47 }
48 Ok(skew)
49 }
50 }
51}
52
53pub fn distance_kurtosis(graph: &Graph) -> IgraphResult<f64> {
73 let moments = distance_moments(graph)?;
74 match moments {
75 None => Ok(0.0),
76 Some((_, variance, _, kurt)) => {
77 if variance < 1e-30 {
78 return Ok(0.0);
79 }
80 Ok(kurt)
81 }
82 }
83}
84
85pub fn diameter_ratio(graph: &Graph) -> IgraphResult<f64> {
102 let n = graph.vcount() as usize;
103 if n < 2 {
104 return Ok(0.0);
105 }
106
107 let diam = compute_diameter_bfs(graph)?;
108 if diam == 0 {
109 return Ok(0.0);
110 }
111
112 Ok(diam as f64 / (n - 1) as f64)
113}
114
115pub fn mean_eccentricity_ratio(graph: &Graph) -> IgraphResult<f64> {
135 let n = graph.vcount() as usize;
136 if n < 2 {
137 return Ok(0.0);
138 }
139
140 let eccs = compute_eccentricities(graph)?;
141 match eccs {
142 None => Ok(0.0),
143 Some(ecc_vec) => {
144 let diam = ecc_vec.iter().copied().max().unwrap_or(0);
145 if diam == 0 {
146 return Ok(0.0);
147 }
148 let mean_ecc = ecc_vec.iter().copied().sum::<u32>() as f64 / n as f64;
149 Ok(mean_ecc / diam as f64)
150 }
151 }
152}
153
154fn distance_moments(graph: &Graph) -> IgraphResult<Option<(f64, f64, f64, f64)>> {
157 let n = graph.vcount() as usize;
158 if n < 2 {
159 return Ok(None);
160 }
161
162 let mut sum = 0_u64;
163 let mut count = 0_u64;
164 let mut distances = Vec::new();
165
166 for v in 0..n {
167 let dist = bfs_distances(graph, v)?;
168 for u in (v + 1)..n {
169 if dist[u] == u32::MAX {
170 return Ok(None);
171 }
172 let d = dist[u] as u64;
173 sum += d;
174 count += 1;
175 distances.push(d as f64);
176 }
177 }
178
179 if count < 2 {
180 return Ok(None);
181 }
182
183 let mean = sum as f64 / count as f64;
184
185 let mut m2 = 0.0_f64;
186 let mut m3 = 0.0_f64;
187 let mut m4 = 0.0_f64;
188 for &d in &distances {
189 let diff = d - mean;
190 let d2 = diff * diff;
191 m2 += d2;
192 m3 += d2 * diff;
193 m4 += d2 * d2;
194 }
195 m2 /= count as f64;
196 m3 /= count as f64;
197 m4 /= count as f64;
198
199 let variance = m2;
200 let skewness = if variance < 1e-30 {
201 0.0
202 } else {
203 m3 / (variance * variance.sqrt())
204 };
205 let kurtosis = if variance < 1e-30 {
206 0.0
207 } else {
208 m4 / (variance * variance) - 3.0
209 };
210
211 Ok(Some((mean, variance, skewness, kurtosis)))
212}
213
214fn bfs_distances(graph: &Graph, source: usize) -> IgraphResult<Vec<u32>> {
216 let n = graph.vcount() as usize;
217 let mut dist = vec![u32::MAX; n];
218 dist[source] = 0;
219 let mut queue = std::collections::VecDeque::new();
220 queue.push_back(source);
221 while let Some(v) = queue.pop_front() {
222 let cd = dist[v];
223 let nbrs = graph.neighbors(v as u32)?;
224 for &u in &nbrs {
225 let ui = u as usize;
226 if dist[ui] == u32::MAX {
227 dist[ui] = cd + 1;
228 queue.push_back(ui);
229 }
230 }
231 }
232 Ok(dist)
233}
234
235fn compute_diameter_bfs(graph: &Graph) -> IgraphResult<u32> {
237 let n = graph.vcount() as usize;
238 let mut diam = 0_u32;
239 for v in 0..n {
240 let dist = bfs_distances(graph, v)?;
241 for u in (v + 1)..n {
242 if dist[u] == u32::MAX {
243 return Ok(0);
244 }
245 if dist[u] > diam {
246 diam = dist[u];
247 }
248 }
249 }
250 Ok(diam)
251}
252
253fn compute_eccentricities(graph: &Graph) -> IgraphResult<Option<Vec<u32>>> {
255 let n = graph.vcount() as usize;
256 let mut eccs = vec![0_u32; n];
257 for v in 0..n {
258 let dist = bfs_distances(graph, v)?;
259 let mut max_d = 0_u32;
260 for u in 0..n {
261 if u == v {
262 continue;
263 }
264 if dist[u] == u32::MAX {
265 return Ok(None);
266 }
267 if dist[u] > max_d {
268 max_d = dist[u];
269 }
270 }
271 eccs[v] = max_d;
272 }
273 Ok(Some(eccs))
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 fn empty() -> Graph {
281 Graph::with_vertices(0)
282 }
283
284 fn single() -> Graph {
285 Graph::with_vertices(1)
286 }
287
288 fn single_edge() -> Graph {
289 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
290 }
291
292 fn path3() -> Graph {
293 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
294 }
295
296 fn path4() -> Graph {
297 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
298 }
299
300 fn k3() -> Graph {
301 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
302 }
303
304 fn k4() -> Graph {
305 Graph::from_edges(
306 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
307 false,
308 Some(4),
309 )
310 .unwrap()
311 }
312
313 fn cycle4() -> Graph {
314 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
315 }
316
317 fn cycle5() -> Graph {
318 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
319 }
320
321 fn star5() -> Graph {
322 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
323 }
324
325 fn disconnected() -> Graph {
326 Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap()
327 }
328
329 #[test]
332 fn ds_empty() {
333 assert!(distance_skewness(&empty()).unwrap().abs() < 1e-10);
334 }
335
336 #[test]
337 fn ds_single() {
338 assert!(distance_skewness(&single()).unwrap().abs() < 1e-10);
339 }
340
341 #[test]
342 fn ds_single_edge() {
343 assert!(distance_skewness(&single_edge()).unwrap().abs() < 1e-10);
345 }
346
347 #[test]
348 fn ds_k3() {
349 assert!(distance_skewness(&k3()).unwrap().abs() < 1e-10);
351 }
352
353 #[test]
354 fn ds_k4() {
355 assert!(distance_skewness(&k4()).unwrap().abs() < 1e-10);
356 }
357
358 #[test]
359 fn ds_cycle4() {
360 let s = distance_skewness(&cycle4()).unwrap();
363 assert!(s > -1e-10); }
365
366 #[test]
367 fn ds_disconnected() {
368 assert!(distance_skewness(&disconnected()).unwrap().abs() < 1e-10);
369 }
370
371 #[test]
372 fn ds_path3() {
373 let s = distance_skewness(&path3()).unwrap();
376 assert!(s.is_finite());
377 }
378
379 #[test]
382 fn dk_empty() {
383 assert!(distance_kurtosis(&empty()).unwrap().abs() < 1e-10);
384 }
385
386 #[test]
387 fn dk_single() {
388 assert!(distance_kurtosis(&single()).unwrap().abs() < 1e-10);
389 }
390
391 #[test]
392 fn dk_k4() {
393 assert!(distance_kurtosis(&k4()).unwrap().abs() < 1e-10);
395 }
396
397 #[test]
398 fn dk_disconnected() {
399 assert!(distance_kurtosis(&disconnected()).unwrap().abs() < 1e-10);
400 }
401
402 #[test]
403 fn dk_path4() {
404 let k = distance_kurtosis(&path4()).unwrap();
406 assert!(k.is_finite());
407 }
408
409 #[test]
410 fn dk_finite() {
411 for g in &[path3(), k3(), k4(), cycle4(), cycle5(), star5()] {
412 assert!(distance_kurtosis(g).unwrap().is_finite());
413 }
414 }
415
416 #[test]
419 fn dr_empty() {
420 assert!(diameter_ratio(&empty()).unwrap().abs() < 1e-10);
421 }
422
423 #[test]
424 fn dr_single() {
425 assert!(diameter_ratio(&single()).unwrap().abs() < 1e-10);
426 }
427
428 #[test]
429 fn dr_single_edge() {
430 assert!((diameter_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
432 }
433
434 #[test]
435 fn dr_path3() {
436 assert!((diameter_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
438 }
439
440 #[test]
441 fn dr_path4() {
442 assert!((diameter_ratio(&path4()).unwrap() - 1.0).abs() < 1e-10);
444 }
445
446 #[test]
447 fn dr_k3() {
448 assert!((diameter_ratio(&k3()).unwrap() - 0.5).abs() < 1e-10);
450 }
451
452 #[test]
453 fn dr_k4() {
454 assert!((diameter_ratio(&k4()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
456 }
457
458 #[test]
459 fn dr_cycle4() {
460 assert!((diameter_ratio(&cycle4()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
462 }
463
464 #[test]
465 fn dr_star5() {
466 assert!((diameter_ratio(&star5()).unwrap() - 0.5).abs() < 1e-10);
468 }
469
470 #[test]
471 fn dr_disconnected() {
472 assert!(diameter_ratio(&disconnected()).unwrap().abs() < 1e-10);
473 }
474
475 #[test]
476 fn dr_in_01() {
477 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
478 let r = diameter_ratio(g).unwrap();
479 assert!(r >= -1e-10);
480 assert!(r <= 1.0 + 1e-10);
481 }
482 }
483
484 #[test]
487 fn mer_empty() {
488 assert!(mean_eccentricity_ratio(&empty()).unwrap().abs() < 1e-10);
489 }
490
491 #[test]
492 fn mer_single() {
493 assert!(mean_eccentricity_ratio(&single()).unwrap().abs() < 1e-10);
494 }
495
496 #[test]
497 fn mer_k3() {
498 assert!((mean_eccentricity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
500 }
501
502 #[test]
503 fn mer_k4() {
504 assert!((mean_eccentricity_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
505 }
506
507 #[test]
508 fn mer_path3() {
509 assert!((mean_eccentricity_ratio(&path3()).unwrap() - 5.0 / 6.0).abs() < 1e-10);
511 }
512
513 #[test]
514 fn mer_path4() {
515 assert!((mean_eccentricity_ratio(&path4()).unwrap() - 5.0 / 6.0).abs() < 1e-10);
517 }
518
519 #[test]
520 fn mer_cycle4() {
521 assert!((mean_eccentricity_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
523 }
524
525 #[test]
526 fn mer_star5() {
527 assert!((mean_eccentricity_ratio(&star5()).unwrap() - 0.9).abs() < 1e-10);
530 }
531
532 #[test]
533 fn mer_disconnected() {
534 assert!(mean_eccentricity_ratio(&disconnected()).unwrap().abs() < 1e-10);
535 }
536
537 #[test]
538 fn mer_in_01() {
539 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5()] {
540 let r = mean_eccentricity_ratio(g).unwrap();
541 assert!(r >= -1e-10);
542 assert!(r <= 1.0 + 1e-10);
543 }
544 }
545
546 #[test]
549 fn complete_zero_skew_and_kurt() {
550 assert!(distance_skewness(&k3()).unwrap().abs() < 1e-10);
551 assert!(distance_skewness(&k4()).unwrap().abs() < 1e-10);
552 assert!(distance_kurtosis(&k3()).unwrap().abs() < 1e-10);
553 assert!(distance_kurtosis(&k4()).unwrap().abs() < 1e-10);
554 }
555
556 #[test]
557 fn path_max_diameter_ratio() {
558 assert!((diameter_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
560 assert!((diameter_ratio(&path4()).unwrap() - 1.0).abs() < 1e-10);
561 }
562
563 #[test]
564 fn regular_full_ecc_ratio() {
565 assert!((mean_eccentricity_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
567 assert!((mean_eccentricity_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
568 assert!((mean_eccentricity_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
569 }
570}