rust_igraph/algorithms/properties/
spectral_ratios.rs1#![allow(
13 clippy::cast_possible_truncation,
14 clippy::cast_possible_wrap,
15 clippy::cast_precision_loss,
16 clippy::many_single_char_names,
17 clippy::needless_range_loop,
18 clippy::similar_names,
19 clippy::too_many_lines
20)]
21
22use crate::core::{Graph, IgraphResult};
23
24pub fn degree_spectral_gap_estimate(graph: &Graph) -> IgraphResult<f64> {
42 let n = graph.vcount() as usize;
43 if n < 2 {
44 return Ok(0.0);
45 }
46
47 let mut max_deg = 0_usize;
48 let mut second_deg = 0_usize;
49
50 for v in 0..n {
51 let d = graph.degree(v as u32)?;
52 if d >= max_deg {
53 second_deg = max_deg;
54 max_deg = d;
55 } else if d > second_deg {
56 second_deg = d;
57 }
58 }
59
60 if max_deg == 0 {
61 return Ok(0.0);
62 }
63
64 Ok((max_deg - second_deg) as f64 / max_deg as f64)
65}
66
67pub fn degree_variance_ratio(graph: &Graph) -> IgraphResult<f64> {
84 let n = graph.vcount() as usize;
85 if n < 2 {
86 return Ok(0.0);
87 }
88
89 let mut sum = 0_u64;
90 let mut sum_sq = 0_u64;
91
92 for v in 0..n {
93 let d = graph.degree(v as u32)? as u64;
94 sum += d;
95 sum_sq += d * d;
96 }
97
98 let nf = n as f64;
99 let mean = sum as f64 / nf;
100 let variance = sum_sq as f64 / nf - mean * mean;
101
102 let max_deg = (n - 1) as f64;
103 let var_max = max_deg * max_deg / 4.0;
104
105 if var_max < 1e-15 {
106 return Ok(0.0);
107 }
108
109 Ok((variance / var_max).min(1.0))
110}
111
112pub fn edge_vertex_ratio(graph: &Graph) -> IgraphResult<f64> {
127 let n = graph.vcount();
128 if n == 0 {
129 return Ok(0.0);
130 }
131
132 let m = graph.ecount() as f64;
133 Ok(m / f64::from(n))
134}
135
136pub fn cyclomatic_density(graph: &Graph) -> IgraphResult<f64> {
153 let n = graph.vcount() as usize;
154 if n == 0 {
155 return Ok(0.0);
156 }
157
158 let m = graph.ecount() as i64;
159 let ni = n as i64;
160 let c = count_components(graph)? as i64;
161 let circuit_rank = (m - ni + c).max(0);
162
163 Ok(circuit_rank as f64 / n as f64)
164}
165
166fn count_components(graph: &Graph) -> IgraphResult<usize> {
167 let n = graph.vcount() as usize;
168 if n == 0 {
169 return Ok(0);
170 }
171
172 let mut visited = vec![false; n];
173 let mut count = 0_usize;
174
175 for start in 0..n {
176 if visited[start] {
177 continue;
178 }
179 count += 1;
180 let mut stack = vec![start];
181 visited[start] = true;
182 while let Some(v) = stack.pop() {
183 let neighbors = graph.neighbors(v as u32)?;
184 for &u in &neighbors {
185 let ui = u as usize;
186 if !visited[ui] {
187 visited[ui] = true;
188 stack.push(ui);
189 }
190 }
191 }
192 }
193
194 Ok(count)
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 fn single_edge() -> Graph {
202 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
203 }
204
205 fn path3() -> Graph {
206 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
207 }
208
209 fn k3() -> Graph {
210 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
211 }
212
213 fn k4() -> Graph {
214 Graph::from_edges(
215 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
216 false,
217 Some(4),
218 )
219 .unwrap()
220 }
221
222 fn cycle4() -> Graph {
223 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
224 }
225
226 fn star5() -> Graph {
227 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
228 }
229
230 fn paw() -> Graph {
231 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
232 }
233
234 #[test]
237 fn dsge_empty() {
238 let g = Graph::with_vertices(0);
239 assert!(degree_spectral_gap_estimate(&g).unwrap().abs() < 1e-10);
240 }
241
242 #[test]
243 fn dsge_single() {
244 let g = Graph::with_vertices(1);
245 assert!(degree_spectral_gap_estimate(&g).unwrap().abs() < 1e-10);
246 }
247
248 #[test]
249 fn dsge_isolated() {
250 let g = Graph::with_vertices(5);
251 assert!(degree_spectral_gap_estimate(&g).unwrap().abs() < 1e-10);
252 }
253
254 #[test]
255 fn dsge_single_edge() {
256 assert!(degree_spectral_gap_estimate(&single_edge()).unwrap().abs() < 1e-10);
258 }
259
260 #[test]
261 fn dsge_k3() {
262 assert!(degree_spectral_gap_estimate(&k3()).unwrap().abs() < 1e-10);
264 }
265
266 #[test]
267 fn dsge_k4() {
268 assert!(degree_spectral_gap_estimate(&k4()).unwrap().abs() < 1e-10);
270 }
271
272 #[test]
273 fn dsge_cycle4() {
274 assert!(degree_spectral_gap_estimate(&cycle4()).unwrap().abs() < 1e-10);
276 }
277
278 #[test]
279 fn dsge_star5() {
280 assert!((degree_spectral_gap_estimate(&star5()).unwrap() - 0.75).abs() < 1e-10);
282 }
283
284 #[test]
285 fn dsge_paw() {
286 assert!((degree_spectral_gap_estimate(&paw()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
288 }
289
290 #[test]
291 fn dsge_path3() {
292 assert!((degree_spectral_gap_estimate(&path3()).unwrap() - 0.5).abs() < 1e-10);
294 }
295
296 #[test]
299 fn dvr_empty() {
300 let g = Graph::with_vertices(0);
301 assert!(degree_variance_ratio(&g).unwrap().abs() < 1e-10);
302 }
303
304 #[test]
305 fn dvr_single() {
306 let g = Graph::with_vertices(1);
307 assert!(degree_variance_ratio(&g).unwrap().abs() < 1e-10);
308 }
309
310 #[test]
311 fn dvr_isolated() {
312 let g = Graph::with_vertices(5);
313 assert!(degree_variance_ratio(&g).unwrap().abs() < 1e-10);
314 }
315
316 #[test]
317 fn dvr_k3() {
318 assert!(degree_variance_ratio(&k3()).unwrap().abs() < 1e-10);
320 }
321
322 #[test]
323 fn dvr_k4() {
324 assert!(degree_variance_ratio(&k4()).unwrap().abs() < 1e-10);
326 }
327
328 #[test]
329 fn dvr_cycle4() {
330 assert!(degree_variance_ratio(&cycle4()).unwrap().abs() < 1e-10);
332 }
333
334 #[test]
335 fn dvr_star5() {
336 assert!((degree_variance_ratio(&star5()).unwrap() - 0.36).abs() < 1e-10);
341 }
342
343 #[test]
344 fn dvr_single_edge() {
345 assert!(degree_variance_ratio(&single_edge()).unwrap().abs() < 1e-10);
347 }
348
349 #[test]
350 fn dvr_paw() {
351 assert!((degree_variance_ratio(&paw()).unwrap() - 2.0 / 9.0).abs() < 1e-10);
356 }
357
358 #[test]
361 fn evr_empty() {
362 let g = Graph::with_vertices(0);
363 assert!(edge_vertex_ratio(&g).unwrap().abs() < 1e-10);
364 }
365
366 #[test]
367 fn evr_isolated() {
368 let g = Graph::with_vertices(5);
369 assert!(edge_vertex_ratio(&g).unwrap().abs() < 1e-10);
370 }
371
372 #[test]
373 fn evr_single_edge() {
374 assert!((edge_vertex_ratio(&single_edge()).unwrap() - 0.5).abs() < 1e-10);
376 }
377
378 #[test]
379 fn evr_k3() {
380 assert!((edge_vertex_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
382 }
383
384 #[test]
385 fn evr_k4() {
386 assert!((edge_vertex_ratio(&k4()).unwrap() - 1.5).abs() < 1e-10);
388 }
389
390 #[test]
391 fn evr_cycle4() {
392 assert!((edge_vertex_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
394 }
395
396 #[test]
397 fn evr_star5() {
398 assert!((edge_vertex_ratio(&star5()).unwrap() - 0.8).abs() < 1e-10);
400 }
401
402 #[test]
403 fn evr_path3() {
404 assert!((edge_vertex_ratio(&path3()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
406 }
407
408 #[test]
409 fn evr_paw() {
410 assert!((edge_vertex_ratio(&paw()).unwrap() - 1.0).abs() < 1e-10);
412 }
413
414 #[test]
417 fn cd_empty() {
418 let g = Graph::with_vertices(0);
419 assert!(cyclomatic_density(&g).unwrap().abs() < 1e-10);
420 }
421
422 #[test]
423 fn cd_isolated() {
424 let g = Graph::with_vertices(5);
425 assert!(cyclomatic_density(&g).unwrap().abs() < 1e-10);
426 }
427
428 #[test]
429 fn cd_single_edge() {
430 assert!(cyclomatic_density(&single_edge()).unwrap().abs() < 1e-10);
432 }
433
434 #[test]
435 fn cd_path3() {
436 assert!(cyclomatic_density(&path3()).unwrap().abs() < 1e-10);
438 }
439
440 #[test]
441 fn cd_k3() {
442 assert!((cyclomatic_density(&k3()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
444 }
445
446 #[test]
447 fn cd_k4() {
448 assert!((cyclomatic_density(&k4()).unwrap() - 0.75).abs() < 1e-10);
450 }
451
452 #[test]
453 fn cd_cycle4() {
454 assert!((cyclomatic_density(&cycle4()).unwrap() - 0.25).abs() < 1e-10);
456 }
457
458 #[test]
459 fn cd_star5() {
460 assert!(cyclomatic_density(&star5()).unwrap().abs() < 1e-10);
462 }
463
464 #[test]
465 fn cd_paw() {
466 assert!((cyclomatic_density(&paw()).unwrap() - 0.25).abs() < 1e-10);
468 }
469
470 #[test]
473 fn dsge_in_01() {
474 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
475 let r = degree_spectral_gap_estimate(g).unwrap();
476 assert!(r >= -1e-10);
477 assert!(r <= 1.0 + 1e-10);
478 }
479 }
480
481 #[test]
482 fn dvr_in_01() {
483 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
484 let r = degree_variance_ratio(g).unwrap();
485 assert!(r >= -1e-10);
486 assert!(r <= 1.0 + 1e-10);
487 }
488 }
489
490 #[test]
491 fn evr_nonneg() {
492 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
493 assert!(edge_vertex_ratio(g).unwrap() >= -1e-10);
494 }
495 }
496
497 #[test]
498 fn cd_nonneg() {
499 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
500 assert!(cyclomatic_density(g).unwrap() >= -1e-10);
501 }
502 }
503
504 #[test]
505 fn regular_graphs_zero_variance() {
506 assert!(degree_variance_ratio(&k3()).unwrap().abs() < 1e-10);
507 assert!(degree_variance_ratio(&k4()).unwrap().abs() < 1e-10);
508 assert!(degree_variance_ratio(&cycle4()).unwrap().abs() < 1e-10);
509 }
510
511 #[test]
512 fn regular_graphs_zero_gap() {
513 assert!(degree_spectral_gap_estimate(&k3()).unwrap().abs() < 1e-10);
514 assert!(degree_spectral_gap_estimate(&k4()).unwrap().abs() < 1e-10);
515 assert!(degree_spectral_gap_estimate(&cycle4()).unwrap().abs() < 1e-10);
516 }
517
518 #[test]
519 fn trees_zero_cyclomatic() {
520 assert!(cyclomatic_density(&path3()).unwrap().abs() < 1e-10);
521 assert!(cyclomatic_density(&star5()).unwrap().abs() < 1e-10);
522 assert!(cyclomatic_density(&single_edge()).unwrap().abs() < 1e-10);
523 }
524}