rust_igraph/algorithms/properties/
degree_moments.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::too_many_lines
17)]
18
19use crate::core::{Graph, IgraphResult};
20
21pub fn degree_skewness(graph: &Graph) -> IgraphResult<f64> {
39 let n = graph.vcount() as usize;
40 if n < 3 {
41 return Ok(0.0);
42 }
43
44 let degrees = collect_degrees(graph)?;
45 let mean = mean_of(°rees);
46 let variance = variance_of(°rees, mean);
47
48 if variance <= 0.0 {
49 return Ok(0.0);
50 }
51
52 let sigma = variance.sqrt();
53 let mut m3 = 0.0_f64;
54 for &d in °rees {
55 let z = (d - mean) / sigma;
56 m3 += z * z * z;
57 }
58
59 Ok(m3 / n as f64)
60}
61
62pub fn degree_kurtosis(graph: &Graph) -> IgraphResult<f64> {
83 let n = graph.vcount() as usize;
84 if n < 3 {
85 return Ok(0.0);
86 }
87
88 let degrees = collect_degrees(graph)?;
89 let mean = mean_of(°rees);
90 let variance = variance_of(°rees, mean);
91
92 if variance <= 0.0 {
93 return Ok(0.0);
94 }
95
96 let sigma = variance.sqrt();
97 let mut m4 = 0.0_f64;
98 for &d in °rees {
99 let z = (d - mean) / sigma;
100 let z2 = z * z;
101 m4 += z2 * z2;
102 }
103
104 Ok(m4 / n as f64 - 3.0)
105}
106
107pub fn degree_gini(graph: &Graph) -> IgraphResult<f64> {
125 let n = graph.vcount() as usize;
126 if n < 2 {
127 return Ok(0.0);
128 }
129
130 let degrees = collect_degrees(graph)?;
131 let mean = mean_of(°rees);
132
133 if mean <= 0.0 {
134 return Ok(0.0);
135 }
136
137 let mut sum_abs_diff = 0.0_f64;
138 for i in 0..n {
139 for j in 0..n {
140 sum_abs_diff += (degrees[i] - degrees[j]).abs();
141 }
142 }
143
144 let nf = n as f64;
145 Ok(sum_abs_diff / (2.0 * nf * nf * mean))
146}
147
148pub fn degree_max_deviation(graph: &Graph) -> IgraphResult<f64> {
165 let n = graph.vcount() as usize;
166 if n == 0 {
167 return Ok(0.0);
168 }
169
170 let degrees = collect_degrees(graph)?;
171 let mean = mean_of(°rees);
172
173 let mut max_dev = 0.0_f64;
174 for &d in °rees {
175 let dev = (d - mean).abs();
176 if dev > max_dev {
177 max_dev = dev;
178 }
179 }
180
181 Ok(max_dev)
182}
183
184fn collect_degrees(graph: &Graph) -> IgraphResult<Vec<f64>> {
185 let n = graph.vcount() as usize;
186 let mut degrees = Vec::with_capacity(n);
187 for v in 0..n {
188 degrees.push(graph.degree(v as u32)? as f64);
189 }
190 Ok(degrees)
191}
192
193fn mean_of(vals: &[f64]) -> f64 {
194 if vals.is_empty() {
195 return 0.0;
196 }
197 vals.iter().sum::<f64>() / vals.len() as f64
198}
199
200fn variance_of(vals: &[f64], mean: f64) -> f64 {
201 if vals.is_empty() {
202 return 0.0;
203 }
204 let n = vals.len() as f64;
205 vals.iter().map(|&x| (x - mean) * (x - mean)).sum::<f64>() / n
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 fn single_edge() -> Graph {
213 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
214 }
215
216 fn path3() -> Graph {
217 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
218 }
219
220 fn k3() -> Graph {
221 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
222 }
223
224 fn k4() -> Graph {
225 Graph::from_edges(
226 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
227 false,
228 Some(4),
229 )
230 .unwrap()
231 }
232
233 fn cycle4() -> Graph {
234 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
235 }
236
237 fn star5() -> Graph {
238 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
239 }
240
241 fn paw() -> Graph {
242 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
243 }
244
245 #[test]
248 fn skew_empty() {
249 let g = Graph::with_vertices(0);
250 assert!(degree_skewness(&g).unwrap().abs() < 1e-10);
251 }
252
253 #[test]
254 fn skew_isolated() {
255 let g = Graph::with_vertices(5);
256 assert!(degree_skewness(&g).unwrap().abs() < 1e-10);
257 }
258
259 #[test]
260 fn skew_regular_zero() {
261 assert!(degree_skewness(&k3()).unwrap().abs() < 1e-10);
262 assert!(degree_skewness(&k4()).unwrap().abs() < 1e-10);
263 assert!(degree_skewness(&cycle4()).unwrap().abs() < 1e-10);
264 }
265
266 #[test]
267 fn skew_single_edge() {
268 assert!(degree_skewness(&single_edge()).unwrap().abs() < 1e-10);
269 }
270
271 #[test]
272 fn skew_star5_positive() {
273 assert!(degree_skewness(&star5()).unwrap() > 0.0);
275 }
276
277 #[test]
278 fn skew_path3() {
279 let expected = std::f64::consts::SQRT_2 / 2.0;
292 assert!((degree_skewness(&path3()).unwrap() - expected).abs() < 1e-10);
293 }
294
295 #[test]
296 fn skew_paw() {
297 assert!(degree_skewness(&paw()).unwrap().abs() < 1e-10);
301 }
302
303 #[test]
306 fn kurt_empty() {
307 let g = Graph::with_vertices(0);
308 assert!(degree_kurtosis(&g).unwrap().abs() < 1e-10);
309 }
310
311 #[test]
312 fn kurt_isolated() {
313 let g = Graph::with_vertices(5);
314 assert!(degree_kurtosis(&g).unwrap().abs() < 1e-10);
315 }
316
317 #[test]
318 fn kurt_regular_zero() {
319 assert!(degree_kurtosis(&k3()).unwrap().abs() < 1e-10);
320 assert!(degree_kurtosis(&k4()).unwrap().abs() < 1e-10);
321 assert!(degree_kurtosis(&cycle4()).unwrap().abs() < 1e-10);
322 }
323
324 #[test]
325 fn kurt_single_edge() {
326 assert!(degree_kurtosis(&single_edge()).unwrap().abs() < 1e-10);
328 }
329
330 #[test]
331 fn kurt_path3() {
332 assert!((degree_kurtosis(&path3()).unwrap() - (-1.5)).abs() < 1e-10);
337 }
338
339 #[test]
340 fn kurt_paw() {
341 assert!((degree_kurtosis(&paw()).unwrap() - (-1.0)).abs() < 1e-10);
347 }
348
349 #[test]
350 fn kurt_star5() {
351 assert!((degree_kurtosis(&star5()).unwrap() - 0.25).abs() < 1e-10);
360 }
361
362 #[test]
365 fn gini_empty() {
366 let g = Graph::with_vertices(0);
367 assert!(degree_gini(&g).unwrap().abs() < 1e-10);
368 }
369
370 #[test]
371 fn gini_isolated() {
372 let g = Graph::with_vertices(5);
373 assert!(degree_gini(&g).unwrap().abs() < 1e-10);
374 }
375
376 #[test]
377 fn gini_regular_zero() {
378 assert!(degree_gini(&k3()).unwrap().abs() < 1e-10);
379 assert!(degree_gini(&k4()).unwrap().abs() < 1e-10);
380 assert!(degree_gini(&cycle4()).unwrap().abs() < 1e-10);
381 }
382
383 #[test]
384 fn gini_single_edge() {
385 assert!(degree_gini(&single_edge()).unwrap().abs() < 1e-10);
386 }
387
388 #[test]
389 fn gini_star5() {
390 assert!((degree_gini(&star5()).unwrap() - 0.3).abs() < 1e-10);
396 }
397
398 #[test]
399 fn gini_path3() {
400 let expected = 1.0 / 6.0;
408 assert!((degree_gini(&path3()).unwrap() - expected).abs() < 1e-10);
409 }
410
411 #[test]
412 fn gini_paw() {
413 let expected = 3.0 / 16.0;
422 assert!((degree_gini(&paw()).unwrap() - expected).abs() < 1e-10);
423 }
424
425 #[test]
426 fn gini_in_01() {
427 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
428 let val = degree_gini(g).unwrap();
429 assert!(val >= -1e-10);
430 assert!(val <= 1.0 + 1e-10);
431 }
432 }
433
434 #[test]
437 fn maxdev_empty() {
438 let g = Graph::with_vertices(0);
439 assert!(degree_max_deviation(&g).unwrap().abs() < 1e-10);
440 }
441
442 #[test]
443 fn maxdev_isolated() {
444 let g = Graph::with_vertices(5);
445 assert!(degree_max_deviation(&g).unwrap().abs() < 1e-10);
446 }
447
448 #[test]
449 fn maxdev_regular_zero() {
450 assert!(degree_max_deviation(&k3()).unwrap().abs() < 1e-10);
451 assert!(degree_max_deviation(&k4()).unwrap().abs() < 1e-10);
452 assert!(degree_max_deviation(&cycle4()).unwrap().abs() < 1e-10);
453 }
454
455 #[test]
456 fn maxdev_single_edge() {
457 assert!(degree_max_deviation(&single_edge()).unwrap().abs() < 1e-10);
458 }
459
460 #[test]
461 fn maxdev_star5() {
462 assert!((degree_max_deviation(&star5()).unwrap() - 2.4).abs() < 1e-10);
464 }
465
466 #[test]
467 fn maxdev_path3() {
468 let expected = 2.0 / 3.0;
470 assert!((degree_max_deviation(&path3()).unwrap() - expected).abs() < 1e-10);
471 }
472
473 #[test]
474 fn maxdev_paw() {
475 assert!((degree_max_deviation(&paw()).unwrap() - 1.0).abs() < 1e-10);
477 }
478
479 #[test]
480 fn maxdev_nonnegative() {
481 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
482 assert!(degree_max_deviation(g).unwrap() >= -1e-10);
483 }
484 }
485
486 #[test]
489 fn skew_kurt_consistent() {
490 assert!(degree_skewness(&paw()).unwrap().abs() < 1e-10);
493 }
494
495 #[test]
496 fn gini_zero_for_regular() {
497 for g in &[k3(), k4(), cycle4()] {
499 assert!(degree_gini(g).unwrap().abs() < 1e-10);
500 }
501 }
502}