1#![allow(
10 clippy::cast_possible_truncation,
11 clippy::cast_sign_loss,
12 clippy::cast_possible_wrap,
13 clippy::many_single_char_names,
14 clippy::too_many_lines,
15 clippy::similar_names,
16 clippy::module_name_repetitions
17)]
18
19use crate::core::error::{IgraphError, IgraphResult};
20use crate::core::graph::{Graph, VertexId};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct McsResult {
25 pub alpha: Vec<u32>,
28 pub alpham1: Vec<u32>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct ChordalResult {
37 pub chordal: bool,
39 pub fill_in: Vec<(u32, u32)>,
43}
44
45pub fn maximum_cardinality_search(graph: &Graph) -> IgraphResult<McsResult> {
83 let n = graph.vcount() as usize;
84
85 if n == 0 {
86 return Ok(McsResult {
87 alpha: vec![],
88 alpham1: vec![],
89 });
90 }
91
92 let adj = build_simple_adj(graph)?;
94
95 let mut alpha = vec![0u32; n];
96 let mut alpham1 = vec![0u32; n];
97
98 let mut size = vec![0i32; n];
102 let mut head = vec![0u32; n];
103 let mut next = vec![0u32; n];
104 let mut prev = vec![0u32; n];
105
106 head[0] = 1; for v in 0..n {
109 next[v] = u32::try_from(v + 2).unwrap_or(0);
110 prev[v] = v as u32;
111 }
112 next[n - 1] = 0;
113
114 let mut i = n; let mut j: i32 = 0; while i >= 1 {
118 let v = (head[j as usize])
120 .checked_sub(1)
121 .ok_or_else(|| IgraphError::InvalidArgument("MCS: empty bucket unexpectedly".into()))?
122 as usize;
123
124 let x = next[v];
125 head[j as usize] = x;
126 if x != 0 {
127 prev[(x - 1) as usize] = 0;
128 }
129
130 i -= 1;
131 alpha[v] = i as u32;
132 alpham1[i] = v as u32;
133 size[v] = -1; for &w in &adj[v] {
138 let wu = w as usize;
139 let ws = size[wu];
140 if ws < 0 {
141 continue;
142 }
143
144 let nw = next[wu];
146 let pw = prev[wu];
147 if nw != 0 {
148 prev[(nw - 1) as usize] = pw;
149 }
150 if pw != 0 {
151 next[(pw - 1) as usize] = nw;
152 } else {
153 head[ws as usize] = nw;
154 }
155
156 size[wu] += 1;
157 let ws_new = size[wu] as usize;
158
159 let nw_new = head[ws_new];
161 next[wu] = nw_new;
162 prev[wu] = 0;
163 if nw_new != 0 {
164 prev[(nw_new - 1) as usize] = w + 1;
165 }
166 head[ws_new] = w + 1;
167 }
168
169 j += 1;
170
171 if (j as usize) < n {
173 while j >= 0 && head[j as usize] == 0 {
174 j -= 1;
175 }
176 }
177 }
178
179 Ok(McsResult { alpha, alpham1 })
180}
181
182pub fn is_chordal(graph: &Graph, alpha: Option<&[u32]>) -> IgraphResult<ChordalResult> {
228 let n = graph.vcount() as usize;
229
230 if n == 0 {
231 return Ok(ChordalResult {
232 chordal: true,
233 fill_in: vec![],
234 });
235 }
236
237 if let Some(a) = alpha {
238 if a.len() != n {
239 return Err(IgraphError::InvalidArgument(format!(
240 "is_chordal: alpha length {} != vcount {}",
241 a.len(),
242 n
243 )));
244 }
245 let mut inv = vec![0u32; n];
246 for (v, &rank) in a.iter().enumerate() {
247 let ri = rank as usize;
248 if ri >= n {
249 return Err(IgraphError::InvalidArgument(format!(
250 "is_chordal: alpha[{v}] = {rank} out of range [0, {n})"
251 )));
252 }
253 inv[ri] = v as u32;
254 }
255 is_chordal_impl(graph, n, a, &inv)
256 } else {
257 let mcs = maximum_cardinality_search(graph)?;
258 is_chordal_impl(graph, n, &mcs.alpha, &mcs.alpham1)
259 }
260}
261
262fn is_chordal_impl(
263 graph: &Graph,
264 n: usize,
265 alpha: &[u32],
266 alpham1: &[u32],
267) -> IgraphResult<ChordalResult> {
268 let adj = build_simple_adj(graph)?;
269
270 let mut f = vec![0u32; n]; let mut index = vec![0u32; n];
272 let mut mark = vec![0u32; n]; let mut fill_in: Vec<(u32, u32)> = Vec::new();
274 let mut chordal = true;
275
276 for (i, &alpham1_i) in alpham1.iter().enumerate() {
277 let w = alpham1_i as usize;
278 f[w] = w as u32;
279 index[w] = i as u32;
280
281 for &nei in &adj[w] {
283 mark[nei as usize] = (w + 1) as u32;
284 }
285
286 for &nei in &adj[w] {
288 let v = nei as usize;
289 if alpha[v] as usize >= i {
290 continue;
291 }
292
293 let mut x = v;
294 while (index[x] as usize) < i {
295 index[x] = i as u32;
296
297 if mark[x] != (w + 1) as u32 {
298 chordal = false;
299 fill_in.push((x as u32, w as u32));
300 }
301
302 x = f[x] as usize;
303 }
304
305 if f[x] == x as u32 {
306 f[x] = w as u32;
307 }
308 }
309 }
310
311 Ok(ChordalResult { chordal, fill_in })
312}
313
314fn build_simple_adj(graph: &Graph) -> IgraphResult<Vec<Vec<VertexId>>> {
316 let n = graph.vcount() as usize;
317 let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n];
318
319 let ec = graph.ecount();
320 for e in 0..ec {
321 let eid = u32::try_from(e)
322 .map_err(|_| IgraphError::InvalidArgument("edge count exceeds u32".into()))?;
323 let (u, v) = graph.edge(eid)?;
324 if u == v {
325 continue;
326 }
327 adj[u as usize].push(v);
328 adj[v as usize].push(u);
329 }
330
331 for list in &mut adj {
333 list.sort_unstable();
334 list.dedup();
335 }
336
337 Ok(adj)
338}
339
340#[cfg(test)]
345mod tests {
346 use super::*;
347 use crate::create;
348
349 fn make(n: u32, edges: &[(u32, u32)]) -> Graph {
350 create(edges, n, false).expect("make")
351 }
352
353 #[test]
356 fn test_mcs_empty() {
357 let g = make(0, &[]);
358 let r = maximum_cardinality_search(&g).expect("ok");
359 assert!(r.alpha.is_empty());
360 assert!(r.alpham1.is_empty());
361 }
362
363 #[test]
364 fn test_mcs_singleton() {
365 let g = make(1, &[]);
366 let r = maximum_cardinality_search(&g).expect("ok");
367 assert_eq!(r.alpha, vec![0]);
368 assert_eq!(r.alpham1, vec![0]);
369 }
370
371 #[test]
372 fn test_mcs_triangle() {
373 let g = make(3, &[(0, 1), (1, 2), (2, 0)]);
374 let r = maximum_cardinality_search(&g).expect("ok");
375 assert_eq!(r.alpha.len(), 3);
376 assert_eq!(r.alpham1.len(), 3);
377 for (v, &rank) in r.alpha.iter().enumerate() {
379 assert_eq!(r.alpham1[rank as usize], v as u32);
380 }
381 }
382
383 #[test]
384 fn test_mcs_path() {
385 let g = make(4, &[(0, 1), (1, 2), (2, 3)]);
386 let r = maximum_cardinality_search(&g).expect("ok");
387 for (v, &rank) in r.alpha.iter().enumerate() {
388 assert_eq!(r.alpham1[rank as usize], v as u32);
389 }
390 }
391
392 #[test]
393 fn test_mcs_disconnected() {
394 let g = make(4, &[(0, 1)]);
395 let r = maximum_cardinality_search(&g).expect("ok");
396 assert_eq!(r.alpha.len(), 4);
397 for (v, &rank) in r.alpha.iter().enumerate() {
398 assert_eq!(r.alpham1[rank as usize], v as u32);
399 }
400 }
401
402 #[test]
403 fn test_mcs_self_loops() {
404 let g = make(3, &[(0, 0), (0, 1), (1, 2), (2, 2)]);
405 let r = maximum_cardinality_search(&g).expect("ok");
406 assert_eq!(r.alpha.len(), 3);
407 for (v, &rank) in r.alpha.iter().enumerate() {
408 assert_eq!(r.alpham1[rank as usize], v as u32);
409 }
410 }
411
412 #[test]
415 fn test_chordal_empty() {
416 let g = make(0, &[]);
417 let r = is_chordal(&g, None).expect("ok");
418 assert!(r.chordal);
419 assert!(r.fill_in.is_empty());
420 }
421
422 #[test]
423 fn test_chordal_singleton() {
424 let g = make(1, &[]);
425 let r = is_chordal(&g, None).expect("ok");
426 assert!(r.chordal);
427 assert!(r.fill_in.is_empty());
428 }
429
430 #[test]
431 fn test_chordal_triangle() {
432 let g = make(3, &[(0, 1), (1, 2), (2, 0)]);
433 let r = is_chordal(&g, None).expect("ok");
434 assert!(r.chordal);
435 assert!(r.fill_in.is_empty());
436 }
437
438 #[test]
439 fn test_chordal_complete_k4() {
440 let g = make(4, &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
441 let r = is_chordal(&g, None).expect("ok");
442 assert!(r.chordal);
443 assert!(r.fill_in.is_empty());
444 }
445
446 #[test]
447 fn test_not_chordal_4cycle() {
448 let g = make(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]);
449 let r = is_chordal(&g, None).expect("ok");
450 assert!(!r.chordal);
451 assert!(!r.fill_in.is_empty());
452 }
453
454 #[test]
455 fn test_not_chordal_5cycle() {
456 let g = make(5, &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]);
457 let r = is_chordal(&g, None).expect("ok");
458 assert!(!r.chordal);
459 }
460
461 #[test]
462 fn test_chordal_tree() {
463 let g = make(5, &[(0, 1), (0, 2), (1, 3), (1, 4)]);
464 let r = is_chordal(&g, None).expect("ok");
465 assert!(r.chordal);
466 assert!(r.fill_in.is_empty());
467 }
468
469 #[test]
470 fn test_chordal_isolated_vertices() {
471 let g = make(5, &[]);
472 let r = is_chordal(&g, None).expect("ok");
473 assert!(r.chordal);
474 }
475
476 #[test]
477 fn test_chordal_with_alpha() {
478 let g = make(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]);
479 let mcs = maximum_cardinality_search(&g).expect("mcs");
480 let r = is_chordal(&g, Some(&mcs.alpha)).expect("ok");
481 assert!(!r.chordal);
482 }
483
484 #[test]
485 fn test_chordal_wrong_alpha_size() {
486 let g = make(3, &[(0, 1)]);
487 assert!(is_chordal(&g, Some(&[0, 1])).is_err());
488 }
489
490 #[test]
491 fn test_chordal_igraph_lmu_graph() {
492 let g = make(
494 6,
495 &[
496 (0, 1),
497 (0, 2),
498 (1, 1),
499 (1, 3),
500 (2, 0),
501 (2, 0),
502 (2, 3),
503 (3, 4),
504 (3, 4),
505 ],
506 );
507 let r = is_chordal(&g, None).expect("ok");
508 assert!(!r.chordal);
509 assert!(!r.fill_in.is_empty());
511 }
512
513 #[test]
514 fn test_chordal_directed_ignored() {
515 let g = create(&[(0, 1), (1, 2), (2, 0)], 3, true).expect("directed");
516 let r = is_chordal(&g, None).expect("ok");
517 assert!(r.chordal);
518 }
519
520 #[test]
521 fn test_chordal_fan_graph() {
522 let g = make(5, &[(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (2, 3), (3, 4)]);
524 let r = is_chordal(&g, None).expect("ok");
525 assert!(r.chordal);
526 }
527
528 #[test]
529 fn test_fill_in_makes_chordal() {
530 let g = make(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]);
532 let r = is_chordal(&g, None).expect("ok");
533 assert!(!r.chordal);
534
535 let mut all_edges: Vec<(u32, u32)> = vec![(0, 1), (1, 2), (2, 3), (3, 0)];
537 all_edges.extend(r.fill_in.iter());
538 let g2 = make(4, &all_edges);
539 let r2 = is_chordal(&g2, None).expect("ok");
540 assert!(r2.chordal);
541 }
542}
543
544#[cfg(test)]
549#[cfg(all(test, feature = "proptest-harness"))]
550mod proptests {
551 use super::*;
552 use crate::create;
553 use proptest::prelude::*;
554
555 proptest! {
556 #[test]
557 fn prop_mcs_inverse_property(n in 1u32..30, edge_count in 0usize..60) {
558 let mut edges = Vec::new();
559 let mut rng_state = 0x1234_5678u64;
560 for _ in 0..edge_count {
561 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
562 let u = (rng_state >> 32) as u32 % n;
563 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
564 let v = (rng_state >> 32) as u32 % n;
565 if u != v {
566 edges.push((u, v));
567 }
568 }
569 let g = create(&edges, n, false).expect("graph");
570 let r = maximum_cardinality_search(&g).expect("mcs");
571 prop_assert_eq!(r.alpha.len(), n as usize);
572 prop_assert_eq!(r.alpham1.len(), n as usize);
573 for (v, &rank) in r.alpha.iter().enumerate() {
574 prop_assert!((rank as usize) < n as usize, "rank out of range");
575 prop_assert_eq!(r.alpham1[rank as usize], v as u32, "inverse mismatch");
576 }
577 }
578
579 #[test]
580 fn prop_mcs_ranks_are_permutation(n in 1u32..30, edge_count in 0usize..60) {
581 let mut edges = Vec::new();
582 let mut rng_state = 0xABCD_EF01u64;
583 for _ in 0..edge_count {
584 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
585 let u = (rng_state >> 32) as u32 % n;
586 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
587 let v = (rng_state >> 32) as u32 % n;
588 if u != v {
589 edges.push((u, v));
590 }
591 }
592 let g = create(&edges, n, false).expect("graph");
593 let r = maximum_cardinality_search(&g).expect("mcs");
594 let mut sorted_ranks: Vec<u32> = r.alpha.clone();
595 sorted_ranks.sort_unstable();
596 let expected: Vec<u32> = (0..n).collect();
597 prop_assert_eq!(sorted_ranks, expected, "ranks should be a permutation of 0..n");
598 }
599
600 #[test]
601 fn prop_fill_in_makes_chordal(n in 3u32..20, edge_count in 3usize..40) {
602 let mut edges = Vec::new();
603 let mut rng_state = 0xDEAD_BEEFu64;
604 for _ in 0..edge_count {
605 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
606 let u = (rng_state >> 32) as u32 % n;
607 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
608 let v = (rng_state >> 32) as u32 % n;
609 if u != v {
610 edges.push((u, v));
611 }
612 }
613 let g = create(&edges, n, false).expect("graph");
614 let r = is_chordal(&g, None).expect("chordal");
615 if !r.chordal {
616 let mut all_edges = edges.clone();
617 all_edges.extend(r.fill_in.iter());
618 let g2 = create(&all_edges, n, false).expect("graph2");
619 let r2 = is_chordal(&g2, None).expect("chordal2");
620 prop_assert!(r2.chordal, "adding fill-in should make graph chordal");
621 }
622 }
623
624 #[test]
625 fn prop_tree_is_chordal(n in 2u32..30) {
626 let mut edges = Vec::new();
628 let mut rng_state = 0xCAFE_BABEu64;
629 for v in 1..n {
630 rng_state = rng_state.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
631 let parent = (rng_state >> 32) as u32 % v;
632 edges.push((parent, v));
633 }
634 let g = create(&edges, n, false).expect("tree");
635 let r = is_chordal(&g, None).expect("chordal");
636 prop_assert!(r.chordal, "every tree is chordal");
637 prop_assert!(r.fill_in.is_empty(), "tree needs no fill-in");
638 }
639
640 #[test]
641 fn prop_complete_graph_is_chordal(n in 2u32..15) {
642 let mut edges = Vec::new();
643 for u in 0..n {
644 for v in (u + 1)..n {
645 edges.push((u, v));
646 }
647 }
648 let g = create(&edges, n, false).expect("complete");
649 let r = is_chordal(&g, None).expect("chordal");
650 prop_assert!(r.chordal, "complete graph is chordal");
651 }
652 }
653}