rust_igraph/algorithms/properties/
bipartivity_ratios.rs1#![allow(
15 clippy::cast_possible_truncation,
16 clippy::cast_precision_loss,
17 clippy::many_single_char_names,
18 clippy::needless_range_loop,
19 clippy::similar_names,
20 clippy::too_many_lines
21)]
22
23use crate::core::{Graph, IgraphResult};
24
25pub fn bipartivity_index(graph: &Graph) -> IgraphResult<f64> {
43 let n = graph.vcount() as usize;
44 if n == 0 {
45 return Ok(0.0);
46 }
47
48 let mut color: Vec<i8> = vec![-1; n];
49 let mut side_a = 0_u64;
50 let mut side_b = 0_u64;
51
52 for start in 0..n {
53 if color[start] != -1 {
54 continue;
55 }
56 color[start] = 0;
57 side_a += 1;
58 let mut queue = std::collections::VecDeque::new();
59 queue.push_back(start);
60 while let Some(v) = queue.pop_front() {
61 let nbrs = graph.neighbors(v as u32)?;
62 let next_color = 1 - color[v];
63 for &u in &nbrs {
64 let ui = u as usize;
65 if color[ui] == -1 {
66 color[ui] = next_color;
67 if next_color == 0 {
68 side_a += 1;
69 } else {
70 side_b += 1;
71 }
72 queue.push_back(ui);
73 }
74 }
75 }
76 }
77
78 let larger = side_a.max(side_b);
79 Ok(larger as f64 / n as f64)
80}
81
82pub fn frustration_ratio(graph: &Graph) -> IgraphResult<f64> {
99 let n = graph.vcount() as usize;
100 let m = graph.ecount();
101 if m == 0 {
102 return Ok(0.0);
103 }
104
105 let mut color: Vec<i8> = vec![-1; n];
106
107 for start in 0..n {
108 if color[start] != -1 {
109 continue;
110 }
111 color[start] = 0;
112 let mut queue = std::collections::VecDeque::new();
113 queue.push_back(start);
114 while let Some(v) = queue.pop_front() {
115 let nbrs = graph.neighbors(v as u32)?;
116 let next_color = 1 - color[v];
117 for &u in &nbrs {
118 let ui = u as usize;
119 if color[ui] == -1 {
120 color[ui] = next_color;
121 queue.push_back(ui);
122 }
123 }
124 }
125 }
126
127 let mut frustrated = 0_u64;
128 for v in 0..n {
129 let nbrs = graph.neighbors(v as u32)?;
130 for &u in &nbrs {
131 let ui = u as usize;
132 if ui > v && color[v] == color[ui] {
133 frustrated += 1;
134 }
135 }
136 }
137
138 Ok(frustrated as f64 / m as f64)
139}
140
141pub fn odd_cycle_density(graph: &Graph) -> IgraphResult<f64> {
161 let n = graph.vcount() as usize;
162 if n < 3 {
163 return Ok(0.0);
164 }
165
166 let mut total_triplets = 0_u64;
167 let mut closed_triplets = 0_u64;
168
169 for v in 0..n {
170 let neighbors = graph.neighbors(v as u32)?;
171 let d = neighbors.len();
172 if d < 2 {
173 continue;
174 }
175 let possible = (d * (d - 1)) / 2;
176 total_triplets += possible as u64;
177
178 for i in 0..d {
179 for j in (i + 1)..d {
180 if graph.has_edge(neighbors[i], neighbors[j]) {
181 closed_triplets += 1;
182 }
183 }
184 }
185 }
186
187 if total_triplets == 0 {
188 return Ok(0.0);
189 }
190
191 Ok(closed_triplets as f64 / total_triplets as f64)
192}
193
194pub fn even_odd_walk_ratio(graph: &Graph) -> IgraphResult<f64> {
216 let n = graph.vcount() as usize;
217 let m = graph.ecount();
218 if m == 0 {
219 return Ok(0.0);
220 }
221
222 let mut sum_deg_sq = 0_u64;
223 for v in 0..n {
224 let d = graph.degree(v as u32)? as u64;
225 sum_deg_sq += d * d;
226 }
227
228 let two_m = 2 * m as u64;
229 Ok(sum_deg_sq as f64 / two_m as f64)
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 fn empty() -> Graph {
237 Graph::with_vertices(0)
238 }
239
240 fn single() -> Graph {
241 Graph::with_vertices(1)
242 }
243
244 fn single_edge() -> Graph {
245 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
246 }
247
248 fn path3() -> Graph {
249 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
250 }
251
252 fn k3() -> Graph {
253 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
254 }
255
256 fn k4() -> Graph {
257 Graph::from_edges(
258 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
259 false,
260 Some(4),
261 )
262 .unwrap()
263 }
264
265 fn cycle4() -> Graph {
266 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
267 }
268
269 fn cycle5() -> Graph {
270 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
271 }
272
273 fn star5() -> Graph {
274 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
275 }
276
277 fn paw() -> Graph {
278 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
279 }
280
281 fn bipartite_k23() -> Graph {
282 Graph::from_edges(
284 &[(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)],
285 false,
286 Some(5),
287 )
288 .unwrap()
289 }
290
291 #[test]
294 fn bi_empty() {
295 assert!(bipartivity_index(&empty()).unwrap().abs() < 1e-10);
296 }
297
298 #[test]
299 fn bi_single() {
300 assert!((bipartivity_index(&single()).unwrap() - 1.0).abs() < 1e-10);
301 }
302
303 #[test]
304 fn bi_single_edge() {
305 assert!((bipartivity_index(&single_edge()).unwrap() - 0.5).abs() < 1e-10);
307 }
308
309 #[test]
310 fn bi_path3() {
311 assert!((bipartivity_index(&path3()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
313 }
314
315 #[test]
316 fn bi_cycle4() {
317 assert!((bipartivity_index(&cycle4()).unwrap() - 0.5).abs() < 1e-10);
319 }
320
321 #[test]
322 fn bi_bipartite_k23() {
323 assert!((bipartivity_index(&bipartite_k23()).unwrap() - 3.0 / 5.0).abs() < 1e-10);
325 }
326
327 #[test]
328 fn bi_in_range() {
329 for g in &[
330 empty(),
331 single(),
332 single_edge(),
333 path3(),
334 k3(),
335 k4(),
336 cycle4(),
337 star5(),
338 paw(),
339 ] {
340 let r = bipartivity_index(g).unwrap();
341 assert!(r >= -1e-10);
342 assert!(r <= 1.0 + 1e-10);
343 }
344 }
345
346 #[test]
349 fn fr_empty() {
350 assert!(frustration_ratio(&empty()).unwrap().abs() < 1e-10);
351 }
352
353 #[test]
354 fn fr_single_edge() {
355 assert!(frustration_ratio(&single_edge()).unwrap().abs() < 1e-10);
357 }
358
359 #[test]
360 fn fr_path3() {
361 assert!(frustration_ratio(&path3()).unwrap().abs() < 1e-10);
363 }
364
365 #[test]
366 fn fr_cycle4() {
367 assert!(frustration_ratio(&cycle4()).unwrap().abs() < 1e-10);
369 }
370
371 #[test]
372 fn fr_bipartite_k23() {
373 assert!(frustration_ratio(&bipartite_k23()).unwrap().abs() < 1e-10);
374 }
375
376 #[test]
377 fn fr_k3() {
378 assert!((frustration_ratio(&k3()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
380 }
381
382 #[test]
383 fn fr_cycle5() {
384 assert!((frustration_ratio(&cycle5()).unwrap() - 1.0 / 5.0).abs() < 1e-10);
386 }
387
388 #[test]
389 fn fr_in_01() {
390 for g in &[
391 single_edge(),
392 path3(),
393 k3(),
394 k4(),
395 cycle4(),
396 cycle5(),
397 star5(),
398 paw(),
399 ] {
400 let r = frustration_ratio(g).unwrap();
401 assert!(r >= -1e-10);
402 assert!(r <= 1.0 + 1e-10);
403 }
404 }
405
406 #[test]
409 fn ocd_empty() {
410 assert!(odd_cycle_density(&empty()).unwrap().abs() < 1e-10);
411 }
412
413 #[test]
414 fn ocd_single() {
415 assert!(odd_cycle_density(&single()).unwrap().abs() < 1e-10);
416 }
417
418 #[test]
419 fn ocd_path3() {
420 assert!(odd_cycle_density(&path3()).unwrap().abs() < 1e-10);
422 }
423
424 #[test]
425 fn ocd_cycle4() {
426 assert!(odd_cycle_density(&cycle4()).unwrap().abs() < 1e-10);
428 }
429
430 #[test]
431 fn ocd_bipartite_k23() {
432 assert!(odd_cycle_density(&bipartite_k23()).unwrap().abs() < 1e-10);
434 }
435
436 #[test]
437 fn ocd_k3() {
438 assert!((odd_cycle_density(&k3()).unwrap() - 1.0).abs() < 1e-10);
440 }
441
442 #[test]
443 fn ocd_k4() {
444 assert!((odd_cycle_density(&k4()).unwrap() - 1.0).abs() < 1e-10);
446 }
447
448 #[test]
449 fn ocd_star5() {
450 assert!(odd_cycle_density(&star5()).unwrap().abs() < 1e-10);
452 }
453
454 #[test]
455 fn ocd_paw() {
456 let r = odd_cycle_density(&paw()).unwrap();
465 assert!((r - 3.0 / 5.0).abs() < 1e-10);
466 }
467
468 #[test]
469 fn ocd_in_01() {
470 for g in &[path3(), k3(), k4(), cycle4(), star5(), paw()] {
471 let r = odd_cycle_density(g).unwrap();
472 assert!(r >= -1e-10);
473 assert!(r <= 1.0 + 1e-10);
474 }
475 }
476
477 #[test]
480 fn eowr_empty() {
481 assert!(even_odd_walk_ratio(&empty()).unwrap().abs() < 1e-10);
482 }
483
484 #[test]
485 fn eowr_single() {
486 assert!(even_odd_walk_ratio(&single()).unwrap().abs() < 1e-10);
487 }
488
489 #[test]
490 fn eowr_single_edge() {
491 assert!((even_odd_walk_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
493 }
494
495 #[test]
496 fn eowr_path3() {
497 assert!((even_odd_walk_ratio(&path3()).unwrap() - 1.5).abs() < 1e-10);
499 }
500
501 #[test]
502 fn eowr_k3() {
503 assert!((even_odd_walk_ratio(&k3()).unwrap() - 2.0).abs() < 1e-10);
505 }
506
507 #[test]
508 fn eowr_k4() {
509 assert!((even_odd_walk_ratio(&k4()).unwrap() - 3.0).abs() < 1e-10);
511 }
512
513 #[test]
514 fn eowr_cycle4() {
515 assert!((even_odd_walk_ratio(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
517 }
518
519 #[test]
520 fn eowr_star5() {
521 assert!((even_odd_walk_ratio(&star5()).unwrap() - 2.5).abs() < 1e-10);
523 }
524
525 #[test]
526 fn eowr_positive() {
527 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
528 assert!(even_odd_walk_ratio(g).unwrap() > 0.0);
529 }
530 }
531
532 #[test]
535 fn bipartite_zero_frustration() {
536 assert!(frustration_ratio(&path3()).unwrap().abs() < 1e-10);
537 assert!(frustration_ratio(&cycle4()).unwrap().abs() < 1e-10);
538 assert!(frustration_ratio(&star5()).unwrap().abs() < 1e-10);
539 assert!(frustration_ratio(&bipartite_k23()).unwrap().abs() < 1e-10);
540 }
541
542 #[test]
543 fn bipartite_zero_odd_cycles() {
544 assert!(odd_cycle_density(&path3()).unwrap().abs() < 1e-10);
545 assert!(odd_cycle_density(&cycle4()).unwrap().abs() < 1e-10);
546 assert!(odd_cycle_density(&star5()).unwrap().abs() < 1e-10);
547 assert!(odd_cycle_density(&bipartite_k23()).unwrap().abs() < 1e-10);
548 }
549
550 #[test]
551 fn regular_graph_walk_ratio_equals_degree() {
552 assert!((even_odd_walk_ratio(&k3()).unwrap() - 2.0).abs() < 1e-10);
554 assert!((even_odd_walk_ratio(&k4()).unwrap() - 3.0).abs() < 1e-10);
555 assert!((even_odd_walk_ratio(&cycle4()).unwrap() - 2.0).abs() < 1e-10);
556 }
557}