rust_igraph/algorithms/properties/
degree_spread.rs1#![allow(
12 clippy::cast_possible_truncation,
13 clippy::cast_precision_loss,
14 clippy::cast_sign_loss,
15 clippy::many_single_char_names,
16 clippy::needless_range_loop,
17 clippy::too_many_lines
18)]
19
20use crate::core::{Graph, IgraphResult};
21
22pub fn degree_range(graph: &Graph) -> IgraphResult<usize> {
39 let n = graph.vcount() as usize;
40 if n == 0 {
41 return Ok(0);
42 }
43
44 let mut d_min = usize::MAX;
45 let mut d_max = 0_usize;
46 for v in 0..n {
47 let d = graph.degree(v as u32)?;
48 if d < d_min {
49 d_min = d;
50 }
51 if d > d_max {
52 d_max = d;
53 }
54 }
55
56 Ok(d_max - d_min)
57}
58
59pub fn degree_span_ratio(graph: &Graph) -> IgraphResult<f64> {
76 let n = graph.vcount() as usize;
77 if n == 0 {
78 return Ok(0.0);
79 }
80
81 let mut d_min = usize::MAX;
82 let mut d_max = 0_usize;
83 let mut sum = 0_usize;
84 for v in 0..n {
85 let d = graph.degree(v as u32)?;
86 if d < d_min {
87 d_min = d;
88 }
89 if d > d_max {
90 d_max = d;
91 }
92 sum += d;
93 }
94
95 let mean = sum as f64 / n as f64;
96 if mean <= 0.0 {
97 return Ok(0.0);
98 }
99
100 Ok((d_max - d_min) as f64 / mean)
101}
102
103pub fn degree_median(graph: &Graph) -> IgraphResult<f64> {
118 let n = graph.vcount() as usize;
119 if n == 0 {
120 return Ok(0.0);
121 }
122
123 let mut degrees = Vec::with_capacity(n);
124 for v in 0..n {
125 degrees.push(graph.degree(v as u32)?);
126 }
127 degrees.sort_unstable();
128
129 if n % 2 == 1 {
130 Ok(degrees[n / 2] as f64)
131 } else {
132 Ok(f64::midpoint(
133 degrees[n / 2 - 1] as f64,
134 degrees[n / 2] as f64,
135 ))
136 }
137}
138
139pub fn degree_iqr(graph: &Graph) -> IgraphResult<f64> {
156 let n = graph.vcount() as usize;
157 if n < 2 {
158 return Ok(0.0);
159 }
160
161 let mut degrees = Vec::with_capacity(n);
162 for v in 0..n {
163 degrees.push(graph.degree(v as u32)? as f64);
164 }
165 degrees.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
166
167 let q1 = percentile(°rees, 0.25);
168 let q3 = percentile(°rees, 0.75);
169
170 Ok(q3 - q1)
171}
172
173fn percentile(sorted: &[f64], p: f64) -> f64 {
174 let n = sorted.len();
175 if n == 0 {
176 return 0.0;
177 }
178 if n == 1 {
179 return sorted[0];
180 }
181
182 let idx = p * (n - 1) as f64;
183 let lo = idx.floor() as usize;
184 let hi = idx.ceil() as usize;
185
186 if lo == hi {
187 sorted[lo]
188 } else {
189 let frac = idx - lo as f64;
190 sorted[lo] * (1.0 - frac) + sorted[hi] * frac
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 fn single_edge() -> Graph {
199 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
200 }
201
202 fn path3() -> Graph {
203 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
204 }
205
206 fn k3() -> Graph {
207 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
208 }
209
210 fn k4() -> Graph {
211 Graph::from_edges(
212 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
213 false,
214 Some(4),
215 )
216 .unwrap()
217 }
218
219 fn cycle4() -> Graph {
220 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
221 }
222
223 fn star5() -> Graph {
224 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
225 }
226
227 fn paw() -> Graph {
228 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
229 }
230
231 #[test]
234 fn range_empty() {
235 let g = Graph::with_vertices(0);
236 assert_eq!(degree_range(&g).unwrap(), 0);
237 }
238
239 #[test]
240 fn range_isolated() {
241 let g = Graph::with_vertices(5);
242 assert_eq!(degree_range(&g).unwrap(), 0);
243 }
244
245 #[test]
246 fn range_regular_zero() {
247 assert_eq!(degree_range(&k3()).unwrap(), 0);
248 assert_eq!(degree_range(&k4()).unwrap(), 0);
249 assert_eq!(degree_range(&cycle4()).unwrap(), 0);
250 }
251
252 #[test]
253 fn range_single_edge() {
254 assert_eq!(degree_range(&single_edge()).unwrap(), 0);
255 }
256
257 #[test]
258 fn range_star5() {
259 assert_eq!(degree_range(&star5()).unwrap(), 3);
260 }
261
262 #[test]
263 fn range_path3() {
264 assert_eq!(degree_range(&path3()).unwrap(), 1);
265 }
266
267 #[test]
268 fn range_paw() {
269 assert_eq!(degree_range(&paw()).unwrap(), 2);
271 }
272
273 #[test]
276 fn span_empty() {
277 let g = Graph::with_vertices(0);
278 assert!(degree_span_ratio(&g).unwrap().abs() < 1e-10);
279 }
280
281 #[test]
282 fn span_isolated() {
283 let g = Graph::with_vertices(5);
284 assert!(degree_span_ratio(&g).unwrap().abs() < 1e-10);
285 }
286
287 #[test]
288 fn span_regular_zero() {
289 assert!(degree_span_ratio(&k3()).unwrap().abs() < 1e-10);
290 assert!(degree_span_ratio(&k4()).unwrap().abs() < 1e-10);
291 assert!(degree_span_ratio(&cycle4()).unwrap().abs() < 1e-10);
292 }
293
294 #[test]
295 fn span_single_edge() {
296 assert!(degree_span_ratio(&single_edge()).unwrap().abs() < 1e-10);
297 }
298
299 #[test]
300 fn span_star5() {
301 assert!((degree_span_ratio(&star5()).unwrap() - 1.875).abs() < 1e-10);
303 }
304
305 #[test]
306 fn span_path3() {
307 assert!((degree_span_ratio(&path3()).unwrap() - 0.75).abs() < 1e-10);
309 }
310
311 #[test]
312 fn span_paw() {
313 assert!((degree_span_ratio(&paw()).unwrap() - 1.0).abs() < 1e-10);
315 }
316
317 #[test]
318 fn span_nonneg() {
319 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
320 assert!(degree_span_ratio(g).unwrap() >= -1e-10);
321 }
322 }
323
324 #[test]
327 fn median_empty() {
328 let g = Graph::with_vertices(0);
329 assert!(degree_median(&g).unwrap().abs() < 1e-10);
330 }
331
332 #[test]
333 fn median_isolated() {
334 let g = Graph::with_vertices(5);
335 assert!(degree_median(&g).unwrap().abs() < 1e-10);
336 }
337
338 #[test]
339 fn median_k3() {
340 assert!((degree_median(&k3()).unwrap() - 2.0).abs() < 1e-10);
342 }
343
344 #[test]
345 fn median_k4() {
346 assert!((degree_median(&k4()).unwrap() - 3.0).abs() < 1e-10);
348 }
349
350 #[test]
351 fn median_cycle4() {
352 assert!((degree_median(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
354 }
355
356 #[test]
357 fn median_single_edge() {
358 assert!((degree_median(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
360 }
361
362 #[test]
363 fn median_star5() {
364 assert!((degree_median(&star5()).unwrap() - 1.0).abs() < 1e-10);
366 }
367
368 #[test]
369 fn median_path3() {
370 assert!((degree_median(&path3()).unwrap() - 1.0).abs() < 1e-10);
372 }
373
374 #[test]
375 fn median_paw() {
376 assert!((degree_median(&paw()).unwrap() - 2.0).abs() < 1e-10);
378 }
379
380 #[test]
383 fn iqr_empty() {
384 let g = Graph::with_vertices(0);
385 assert!(degree_iqr(&g).unwrap().abs() < 1e-10);
386 }
387
388 #[test]
389 fn iqr_isolated() {
390 let g = Graph::with_vertices(5);
391 assert!(degree_iqr(&g).unwrap().abs() < 1e-10);
392 }
393
394 #[test]
395 fn iqr_regular_zero() {
396 assert!(degree_iqr(&k3()).unwrap().abs() < 1e-10);
397 assert!(degree_iqr(&k4()).unwrap().abs() < 1e-10);
398 assert!(degree_iqr(&cycle4()).unwrap().abs() < 1e-10);
399 }
400
401 #[test]
402 fn iqr_single_edge() {
403 assert!(degree_iqr(&single_edge()).unwrap().abs() < 1e-10);
404 }
405
406 #[test]
407 fn iqr_star5() {
408 assert!(degree_iqr(&star5()).unwrap().abs() < 1e-10);
413 }
414
415 #[test]
416 fn iqr_paw() {
417 assert!((degree_iqr(&paw()).unwrap() - 0.5).abs() < 1e-10);
422 }
423
424 #[test]
425 fn iqr_path3() {
426 assert!((degree_iqr(&path3()).unwrap() - 0.5).abs() < 1e-10);
431 }
432
433 #[test]
434 fn iqr_nonneg() {
435 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
436 assert!(degree_iqr(g).unwrap() >= -1e-10);
437 }
438 }
439
440 #[test]
443 fn range_le_max_degree() {
444 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
445 let n = g.vcount() as usize;
446 let mut d_max = 0_usize;
447 for v in 0..n {
448 let d = g.degree(v as u32).unwrap();
449 if d > d_max {
450 d_max = d;
451 }
452 }
453 assert!(degree_range(g).unwrap() <= d_max);
454 }
455 }
456
457 #[test]
458 fn median_between_min_max() {
459 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
460 let n = g.vcount() as usize;
461 let mut d_min = usize::MAX;
462 let mut d_max = 0_usize;
463 for v in 0..n {
464 let d = g.degree(v as u32).unwrap();
465 if d < d_min {
466 d_min = d;
467 }
468 if d > d_max {
469 d_max = d;
470 }
471 }
472 let med = degree_median(g).unwrap();
473 assert!(med >= d_min as f64 - 1e-10);
474 assert!(med <= d_max as f64 + 1e-10);
475 }
476 }
477}