rust_igraph/algorithms/properties/
extended_irregularity.rs1#![allow(
17 clippy::cast_possible_truncation,
18 clippy::cast_precision_loss,
19 clippy::many_single_char_names,
20 clippy::needless_range_loop,
21 clippy::too_many_lines
22)]
23
24use crate::core::{Graph, IgraphResult};
25
26pub fn bell_index(graph: &Graph) -> IgraphResult<f64> {
49 let n = graph.vcount() as usize;
50 if n == 0 {
51 return Ok(0.0);
52 }
53
54 let m = graph.ecount();
55 let d_bar = 2.0 * m as f64 / n as f64;
56 let mut sum = 0.0_f64;
57
58 for v in 0..n {
59 let d = graph.degree(v as u32)? as f64;
60 let diff = d - d_bar;
61 sum += diff * diff;
62 }
63
64 Ok(sum / n as f64)
65}
66
67pub fn collatz_sinogowitz(graph: &Graph) -> IgraphResult<f64> {
87 let n = graph.vcount() as usize;
88 if n == 0 {
89 return Ok(0.0);
90 }
91 let m = graph.ecount();
92 if m == 0 {
93 return Ok(0.0);
94 }
95
96 let d_bar = 2.0 * m as f64 / n as f64;
97
98 let rho = crate::algorithms::properties::spectral_metrics::spectral_radius(graph)?;
99
100 Ok((rho - d_bar).max(0.0))
101}
102
103pub fn irl_irregularity(graph: &Graph) -> IgraphResult<f64> {
120 let mut result = 0.0_f64;
121
122 for (u, v) in graph.edges() {
123 if u == v {
124 continue;
125 }
126 let du = graph.degree(u)? as f64;
127 let dv = graph.degree(v)? as f64;
128 if du <= 0.0 || dv <= 0.0 {
129 continue;
130 }
131 result += (du.ln() - dv.ln()).abs();
132 }
133
134 Ok(result)
135}
136
137pub fn irlu_irregularity(graph: &Graph) -> IgraphResult<f64> {
157 let mut result = 0.0_f64;
158
159 for (u, v) in graph.edges() {
160 if u == v {
161 continue;
162 }
163 let du = graph.degree(u)? as f64;
164 let dv = graph.degree(v)? as f64;
165 if du <= 0.0 || dv <= 0.0 {
166 continue;
167 }
168 result += (du / dv).ln().abs();
169 }
170
171 Ok(result)
172}
173
174pub fn degree_cv(graph: &Graph) -> IgraphResult<f64> {
193 let n = graph.vcount() as usize;
194 if n == 0 {
195 return Ok(0.0);
196 }
197 let m = graph.ecount();
198 if m == 0 {
199 return Ok(0.0);
200 }
201
202 let d_bar = 2.0 * m as f64 / n as f64;
203 let bell = bell_index(graph)?;
204 let sigma = bell.sqrt();
205
206 Ok(sigma / d_bar)
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 fn single_edge() -> Graph {
214 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
215 }
216
217 fn path3() -> Graph {
218 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
219 }
220
221 fn k3() -> Graph {
222 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
223 }
224
225 fn k4() -> Graph {
226 Graph::from_edges(
227 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
228 false,
229 Some(4),
230 )
231 .unwrap()
232 }
233
234 fn cycle4() -> Graph {
235 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
236 }
237
238 fn star5() -> Graph {
239 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
240 }
241
242 fn paw() -> Graph {
243 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
244 }
245
246 #[test]
249 fn bell_empty() {
250 let g = Graph::with_vertices(0);
251 assert!(bell_index(&g).unwrap().abs() < 1e-10);
252 }
253
254 #[test]
255 fn bell_isolated() {
256 let g = Graph::with_vertices(5);
257 assert!(bell_index(&g).unwrap().abs() < 1e-10);
258 }
259
260 #[test]
261 fn bell_regular_zero() {
262 assert!(bell_index(&k3()).unwrap().abs() < 1e-10);
263 assert!(bell_index(&k4()).unwrap().abs() < 1e-10);
264 assert!(bell_index(&cycle4()).unwrap().abs() < 1e-10);
265 }
266
267 #[test]
268 fn bell_single_edge() {
269 assert!(bell_index(&single_edge()).unwrap().abs() < 1e-10);
271 }
272
273 #[test]
274 fn bell_path3() {
275 assert!((bell_index(&path3()).unwrap() - 2.0 / 9.0).abs() < 1e-10);
279 }
280
281 #[test]
282 fn bell_star5() {
283 assert!((bell_index(&star5()).unwrap() - 1.44).abs() < 1e-10);
287 }
288
289 #[test]
290 fn bell_paw() {
291 assert!((bell_index(&paw()).unwrap() - 0.5).abs() < 1e-10);
295 }
296
297 #[test]
300 fn cs_empty() {
301 let g = Graph::with_vertices(0);
302 assert!(collatz_sinogowitz(&g).unwrap().abs() < 1e-10);
303 }
304
305 #[test]
306 fn cs_isolated() {
307 let g = Graph::with_vertices(5);
308 assert!(collatz_sinogowitz(&g).unwrap().abs() < 1e-10);
309 }
310
311 #[test]
312 fn cs_regular_zero() {
313 assert!(collatz_sinogowitz(&k3()).unwrap().abs() < 1e-10);
315 assert!(collatz_sinogowitz(&k4()).unwrap().abs() < 1e-10);
316 assert!(collatz_sinogowitz(&cycle4()).unwrap().abs() < 1e-10);
317 }
318
319 #[test]
320 fn cs_star5() {
321 assert!((collatz_sinogowitz(&star5()).unwrap() - 0.4).abs() < 1e-10);
324 }
325
326 #[test]
327 fn cs_nonnegative() {
328 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
329 assert!(collatz_sinogowitz(g).unwrap() >= -1e-10);
330 }
331 }
332
333 #[test]
336 fn irl_empty() {
337 let g = Graph::with_vertices(0);
338 assert!(irl_irregularity(&g).unwrap().abs() < 1e-10);
339 }
340
341 #[test]
342 fn irl_isolated() {
343 let g = Graph::with_vertices(5);
344 assert!(irl_irregularity(&g).unwrap().abs() < 1e-10);
345 }
346
347 #[test]
348 fn irl_regular_zero() {
349 assert!(irl_irregularity(&k3()).unwrap().abs() < 1e-10);
350 assert!(irl_irregularity(&k4()).unwrap().abs() < 1e-10);
351 assert!(irl_irregularity(&cycle4()).unwrap().abs() < 1e-10);
352 }
353
354 #[test]
355 fn irl_single_edge() {
356 assert!(irl_irregularity(&single_edge()).unwrap().abs() < 1e-10);
358 }
359
360 #[test]
361 fn irl_star5() {
362 let expected = 4.0 * 4.0_f64.ln();
364 assert!((irl_irregularity(&star5()).unwrap() - expected).abs() < 1e-10);
365 }
366
367 #[test]
368 fn irl_path3() {
369 let expected = 2.0 * 2.0_f64.ln();
371 assert!((irl_irregularity(&path3()).unwrap() - expected).abs() < 1e-10);
372 }
373
374 #[test]
375 fn irl_paw() {
376 let expected = 2.0 * (3.0_f64.ln() - 2.0_f64.ln()) + 3.0_f64.ln();
381 assert!((irl_irregularity(&paw()).unwrap() - expected).abs() < 1e-10);
382 }
383
384 #[test]
387 fn irlu_empty() {
388 let g = Graph::with_vertices(0);
389 assert!(irlu_irregularity(&g).unwrap().abs() < 1e-10);
390 }
391
392 #[test]
393 fn irlu_regular_zero() {
394 assert!(irlu_irregularity(&k3()).unwrap().abs() < 1e-10);
395 assert!(irlu_irregularity(&k4()).unwrap().abs() < 1e-10);
396 }
397
398 #[test]
399 fn irlu_equals_irl() {
400 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
402 let val_irl = irl_irregularity(g).unwrap();
403 let val_irlu = irlu_irregularity(g).unwrap();
404 assert!(
405 (val_irl - val_irlu).abs() < 1e-10,
406 "IRL={val_irl} IRLU={val_irlu}"
407 );
408 }
409 }
410
411 #[test]
412 fn irlu_star5() {
413 let expected = 4.0 * 4.0_f64.ln();
414 assert!((irlu_irregularity(&star5()).unwrap() - expected).abs() < 1e-10);
415 }
416
417 #[test]
420 fn cv_empty() {
421 let g = Graph::with_vertices(0);
422 assert!(degree_cv(&g).unwrap().abs() < 1e-10);
423 }
424
425 #[test]
426 fn cv_isolated() {
427 let g = Graph::with_vertices(5);
428 assert!(degree_cv(&g).unwrap().abs() < 1e-10);
429 }
430
431 #[test]
432 fn cv_regular_zero() {
433 assert!(degree_cv(&k3()).unwrap().abs() < 1e-10);
434 assert!(degree_cv(&k4()).unwrap().abs() < 1e-10);
435 assert!(degree_cv(&cycle4()).unwrap().abs() < 1e-10);
436 }
437
438 #[test]
439 fn cv_single_edge() {
440 assert!(degree_cv(&single_edge()).unwrap().abs() < 1e-10);
442 }
443
444 #[test]
445 fn cv_star5() {
446 assert!((degree_cv(&star5()).unwrap() - 0.75).abs() < 1e-10);
448 }
449
450 #[test]
451 fn cv_paw() {
452 let expected = 0.5_f64.sqrt() / 2.0;
454 assert!((degree_cv(&paw()).unwrap() - expected).abs() < 1e-10);
455 }
456
457 #[test]
458 fn cv_nonnegative() {
459 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
460 assert!(degree_cv(g).unwrap() >= -1e-10);
461 }
462 }
463
464 #[test]
467 fn irl_nonnegative() {
468 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
469 assert!(irl_irregularity(g).unwrap() >= -1e-10);
470 }
471 }
472
473 #[test]
474 fn bell_nonnegative() {
475 for g in &[single_edge(), path3(), k3(), k4(), star5(), paw()] {
476 assert!(bell_index(g).unwrap() >= -1e-10);
477 }
478 }
479}