1#![allow(
2 clippy::cast_possible_truncation,
3 clippy::cast_sign_loss,
4 clippy::cast_possible_wrap,
5 clippy::cast_precision_loss,
6 clippy::many_single_char_names,
7 clippy::too_many_lines,
8 clippy::similar_names,
9 clippy::module_name_repetitions
10)]
11
12use std::collections::VecDeque;
25
26use crate::core::error::{IgraphError, IgraphResult};
27use crate::core::graph::Graph;
28
29#[derive(Debug, Clone)]
50pub struct MatchingResult {
51 pub matching_size: usize,
53 pub matching_weight: f64,
55 pub matching: Vec<Option<u32>>,
57}
58
59pub fn is_matching(
76 graph: &Graph,
77 types: Option<&[bool]>,
78 matching: &[Option<u32>],
79) -> IgraphResult<bool> {
80 let n = graph.vcount() as usize;
81 if matching.len() != n {
82 return Ok(false);
83 }
84
85 let adj = build_undirected_adj(graph);
86
87 for (i, &mi) in matching.iter().enumerate() {
88 let Some(j) = mi else { continue };
89 let j_usize = j as usize;
90 if j_usize >= n {
91 return Ok(false);
92 }
93 if matching[j_usize] != Some(i as u32) {
94 return Ok(false);
95 }
96 if !adj[i].contains(&j) {
97 return Ok(false);
98 }
99 }
100
101 if let Some(t) = types {
102 if t.len() < n {
103 return Err(IgraphError::InvalidArgument(
104 "types vector too short".into(),
105 ));
106 }
107 for (i, &mi) in matching.iter().enumerate() {
108 let Some(j) = mi else { continue };
109 if t[i] == t[j as usize] {
110 return Ok(false);
111 }
112 }
113 }
114
115 Ok(true)
116}
117
118pub fn is_maximal_matching(
132 graph: &Graph,
133 types: Option<&[bool]>,
134 matching: &[Option<u32>],
135) -> IgraphResult<bool> {
136 if !is_matching(graph, types, matching)? {
137 return Ok(false);
138 }
139
140 let n = graph.vcount() as usize;
141 let adj = build_undirected_adj(graph);
142
143 for i in 0..n {
144 if matching[i].is_some() {
145 continue;
146 }
147 for &nb in &adj[i] {
148 if matching[nb as usize].is_none() {
149 if let Some(t) = types {
150 if t[i] == t[nb as usize] {
151 continue;
152 }
153 }
154 return Ok(false);
155 }
156 }
157 }
158
159 Ok(true)
160}
161
162pub fn maximum_bipartite_matching(graph: &Graph, types: &[bool]) -> IgraphResult<MatchingResult> {
181 let n = graph.vcount() as usize;
182 if types.len() < n {
183 return Err(IgraphError::InvalidArgument(
184 "types vector too short".into(),
185 ));
186 }
187
188 let adj = build_undirected_adj(graph);
189 let (matching, num_matched) = push_relabel_unweighted(graph, &adj, types, n)?;
190
191 Ok(MatchingResult {
192 matching_size: num_matched,
193 matching_weight: num_matched as f64,
194 matching,
195 })
196}
197
198pub fn maximum_bipartite_matching_weighted(
216 graph: &Graph,
217 types: &[bool],
218 weights: &[f64],
219 eps: f64,
220) -> IgraphResult<MatchingResult> {
221 let n = graph.vcount() as usize;
222 let ne = graph.ecount();
223 if types.len() < n {
224 return Err(IgraphError::InvalidArgument(
225 "types vector too short".into(),
226 ));
227 }
228 if weights.len() < ne {
229 return Err(IgraphError::InvalidArgument(
230 "weights vector too short".into(),
231 ));
232 }
233 let eps = if eps < 0.0 { 0.0 } else { eps };
234
235 hungarian(graph, types, weights, eps, n, ne)
236}
237
238fn build_undirected_adj(graph: &Graph) -> Vec<Vec<u32>> {
241 let n = graph.vcount() as usize;
242 let mut adj: Vec<Vec<u32>> = vec![Vec::new(); n];
243 for eid in 0..graph.ecount() {
244 if let Ok((u, v)) = graph.edge(eid as u32) {
245 adj[u as usize].push(v);
246 if u != v {
247 adj[v as usize].push(u);
248 }
249 }
250 }
251 adj
252}
253
254fn push_relabel_unweighted(
257 _graph: &Graph,
258 adj: &[Vec<u32>],
259 types: &[bool],
260 n: usize,
261) -> IgraphResult<(Vec<Option<u32>>, usize)> {
262 let mut matching: Vec<i64> = vec![-1; n];
263 let mut labels: Vec<i64> = vec![0; n];
264
265 let count_true = types[..n].iter().filter(|&&t| t).count();
267 let smaller_set = count_true <= n / 2;
268
269 let mut num_matched: usize = 0;
270 for i in 0..n {
271 if matching[i] != -1 {
272 continue;
273 }
274 for &nb in &adj[i] {
275 let nb_usize = nb as usize;
276 if types[nb_usize] == types[i] {
277 return Err(IgraphError::InvalidArgument(
278 "Graph is not bipartite with supplied types vector".into(),
279 ));
280 }
281 if matching[nb_usize] == -1 {
282 matching[nb_usize] = i64::from(i as u32);
283 matching[i] = i64::from(nb);
284 num_matched += 1;
285 break;
286 }
287 }
288 }
289
290 global_relabel(adj, &mut labels, &matching, types, smaller_set, n);
292
293 let mut q: VecDeque<usize> = VecDeque::new();
295 for i in 0..n {
296 if matching[i] == -1 && types[i] == smaller_set {
297 q.push_back(i);
298 }
299 }
300
301 let relabeling_freq = (n / 2).max(1);
302 let mut label_changed: usize = 0;
303
304 while let Some(v) = q.pop_front() {
305 if label_changed >= relabeling_freq {
306 global_relabel(adj, &mut labels, &matching, types, smaller_set, n);
307 label_changed = 0;
308 }
309
310 let mut best_u: i64 = -1;
311 let mut best_label: i64 = 2 * n as i64;
312
313 for &nb in &adj[v] {
314 let nb_usize = nb as usize;
315 if labels[nb_usize] < best_label {
316 best_u = i64::from(nb);
317 best_label = labels[nb_usize];
318 label_changed += 1;
319 }
320 }
321
322 if best_label < n as i64 {
323 let u = best_u as usize;
324 labels[v] = labels[u] + 1;
325 if matching[u] != -1 {
326 let w = matching[u] as usize;
327 if w != v {
328 matching[u] = -1;
329 matching[w] = -1;
330 q.push_back(w);
331 num_matched -= 1;
332 }
333 }
334 matching[u] = v as i64;
335 matching[v] = u as i64;
336 num_matched += 1;
337 labels[u] += 2;
338 label_changed += 1;
339 }
340 }
341
342 let result: Vec<Option<u32>> = matching
343 .iter()
344 .map(|&m| if m < 0 { None } else { Some(m as u32) })
345 .collect();
346
347 Ok((result, num_matched))
348}
349
350fn global_relabel(
351 adj: &[Vec<u32>],
352 labels: &mut [i64],
353 matching: &[i64],
354 types: &[bool],
355 smaller_set: bool,
356 n: usize,
357) {
358 labels.fill(n as i64);
359
360 let mut q: VecDeque<usize> = VecDeque::new();
361 for i in 0..n {
362 if types[i] != smaller_set && matching[i] == -1 {
363 q.push_back(i);
364 labels[i] = 0;
365 }
366 }
367
368 while let Some(v) = q.pop_front() {
369 for &nb in &adj[v] {
370 let w = nb as usize;
371 if labels[w] == n as i64 {
372 labels[w] = labels[v] + 1;
373 let matched_to = matching[w];
374 if matched_to != -1 {
375 let mt = matched_to as usize;
376 if labels[mt] == n as i64 {
377 q.push_back(mt);
378 labels[mt] = labels[w] + 1;
379 }
380 }
381 }
382 }
383 }
384}
385
386fn hungarian(
389 graph: &Graph,
390 types: &[bool],
391 weights: &[f64],
392 eps: f64,
393 n: usize,
394 ne: usize,
395) -> IgraphResult<MatchingResult> {
396 let mut incidence: Vec<Vec<(u32, u32)>> = vec![Vec::new(); n];
398 let mut edges: Vec<(u32, u32)> = Vec::with_capacity(ne);
399 for eid in 0..ne {
400 let (u, v) = graph.edge(eid as u32)?;
401 edges.push((u, v));
402 incidence[u as usize].push((eid as u32, v));
403 if u != v {
404 incidence[v as usize].push((eid as u32, u));
405 }
406 }
407
408 let count_false = types[..n].iter().filter(|&&t| !t).count();
410 let smaller_set_type = count_false > n / 2;
411 let smaller_set_size = if smaller_set_type {
412 n - count_false
413 } else {
414 count_false
415 };
416
417 let mut smaller_set: Vec<usize> = Vec::with_capacity(smaller_set_size);
418 let mut larger_set: Vec<usize> = Vec::with_capacity(n - smaller_set_size);
419 for (i, &tp) in types[..n].iter().enumerate() {
420 if tp == smaller_set_type {
421 smaller_set.push(i);
422 } else {
423 larger_set.push(i);
424 }
425 }
426
427 let mut labels: Vec<f64> = vec![0.0; n];
429 for (i, &tp) in types[..n].iter().enumerate() {
430 if tp != smaller_set_type {
431 continue;
432 }
433 let mut max_w: f64 = 0.0;
434 for &(eid, other) in &incidence[i] {
435 if types[other as usize] == types[i] {
436 return Err(IgraphError::InvalidArgument(
437 "Graph is not bipartite with supplied types vector".into(),
438 ));
439 }
440 if weights[eid as usize] > max_w {
441 max_w = weights[eid as usize];
442 }
443 }
444 labels[i] = max_w;
445 }
446
447 let mut slack: Vec<f64> = vec![0.0; ne];
449 let mut tight_edges: Vec<(u32, u32)> = Vec::new();
450 for eid in 0..ne {
451 let (u, v) = edges[eid];
452 slack[eid] = labels[u as usize] + labels[v as usize] - weights[eid];
453 if slack[eid] <= eps {
454 tight_edges.push((u, v));
455 }
456 }
457
458 let tight_graph = crate::algorithms::constructors::create::create(
460 &tight_edges.iter().map(|&(a, b)| (a, b)).collect::<Vec<_>>(),
461 n as u32,
462 false,
463 )?;
464 let tight_adj = build_undirected_adj(&tight_graph);
465 let (init_match_opt, mut msize) = push_relabel_unweighted(&tight_graph, &tight_adj, types, n)?;
466 let mut matching: Vec<i64> = init_match_opt
467 .iter()
468 .map(|o| match o {
469 Some(v) => i64::from(*v),
470 None => -1,
471 })
472 .collect();
473
474 let mut tight_phantom: Vec<Vec<usize>> = vec![Vec::new(); n];
476
477 while msize < smaller_set_size {
479 let mut parent: Vec<i64> = vec![-1; n];
480 let mut reachable_smaller: Vec<usize> = Vec::new();
481 let mut reachable_larger: Vec<usize> = Vec::new();
482
483 let mut q: VecDeque<usize> = VecDeque::new();
485 for &s in &smaller_set {
486 if matching[s] == -1 {
487 q.push_back(s);
488 parent[s] = s as i64;
489 reachable_smaller.push(s);
490 }
491 }
492
493 let mut alternating_path_endpoint: i64 = -1;
495 'bfs: while let Some(v) = q.pop_front() {
496 for &(eid, other) in &incidence[v] {
498 let u = other as usize;
499 if slack[eid as usize] > eps {
500 continue;
501 }
502 if parent[u] >= 0 {
503 continue;
504 }
505 parent[u] = v as i64;
506 reachable_larger.push(u);
507 let w = matching[u];
508 if w == -1 {
509 alternating_path_endpoint = u as i64;
510 break 'bfs;
511 }
512 let w_usize = w as usize;
513 q.push_back(w_usize);
514 parent[w_usize] = u as i64;
515 reachable_smaller.push(w_usize);
516 }
517
518 for &u in &tight_phantom[v] {
520 if parent[u] >= 0 {
521 continue;
522 }
523 if (labels[v] + labels[u]).abs() > eps {
524 continue;
525 }
526 parent[u] = v as i64;
527 reachable_larger.push(u);
528 let w = matching[u];
529 if w == -1 {
530 alternating_path_endpoint = u as i64;
531 break 'bfs;
532 }
533 let w_usize = w as usize;
534 q.push_back(w_usize);
535 parent[w_usize] = u as i64;
536 reachable_smaller.push(w_usize);
537 }
538 }
539
540 if alternating_path_endpoint != -1 {
541 let mut v = alternating_path_endpoint as usize;
543 let mut u = parent[v] as usize;
544 while u != v {
545 let w = matching[v];
546 if w != -1 {
547 matching[w as usize] = -1;
548 }
549 matching[v] = u as i64;
550 let w2 = matching[u];
551 if w2 != -1 {
552 matching[w2 as usize] = -1;
553 }
554 matching[u] = v as i64;
555
556 v = parent[u] as usize;
557 u = parent[v] as usize;
558 }
559 msize += 1;
560 continue;
561 }
562
563 let mut min_label_larger = f64::INFINITY;
568 for &l in &larger_set {
569 if labels[l] < min_label_larger {
570 min_label_larger = labels[l];
571 }
572 }
573 let mut min_label_reachable_smaller = f64::INFINITY;
574 for &s in &reachable_smaller {
575 if parent[s] >= 0 && labels[s] < min_label_reachable_smaller {
576 min_label_reachable_smaller = labels[s];
577 }
578 }
579 let mut min_slack = min_label_larger + min_label_reachable_smaller;
580
581 for &u in &reachable_smaller {
583 for &(eid, other) in &incidence[u] {
584 let v_node = other as usize;
585 if parent[v_node] >= 0 {
586 continue;
587 }
588 if slack[eid as usize] < min_slack {
589 min_slack = slack[eid as usize];
590 }
591 }
592 }
593
594 if min_slack > 0.0 {
595 for &u in &reachable_smaller {
597 labels[u] -= min_slack;
598 for &(eid, _) in &incidence[u] {
599 slack[eid as usize] -= min_slack;
600 }
601 }
602 for &u in &reachable_larger {
603 labels[u] += min_slack;
604 for &(eid, _) in &incidence[u] {
605 slack[eid as usize] += min_slack;
606 }
607 }
608 }
609
610 for &u in &smaller_set {
612 for &v in &larger_set {
613 if (labels[u] + labels[v]).abs() <= eps {
614 let phantoms = &mut tight_phantom[u];
615 match phantoms.binary_search(&v) {
616 Ok(_) => {} Err(pos) => phantoms.insert(pos, v),
618 }
619 }
620 }
621 }
622 }
623
624 for &u in &smaller_set {
626 let v = matching[u];
627 if v != -1 {
628 let v_usize = v as usize;
629 if tight_phantom[u].binary_search(&v_usize).is_ok() {
630 let is_real = incidence[u]
632 .iter()
633 .any(|&(_, other)| other as usize == v_usize);
634 if !is_real {
635 matching[u] = -1;
636 matching[v_usize] = -1;
637 msize -= 1;
638 }
639 }
640 }
641 }
642
643 let mut total_weight: f64 = 0.0;
645 for eid in 0..ne {
646 if slack[eid] <= eps {
647 let (u, v) = edges[eid];
648 if matching[u as usize] == i64::from(v) {
649 total_weight += weights[eid];
650 }
651 }
652 }
653
654 let result_matching: Vec<Option<u32>> = matching
655 .iter()
656 .map(|&m| if m < 0 { None } else { Some(m as u32) })
657 .collect();
658
659 Ok(MatchingResult {
660 matching_size: msize,
661 matching_weight: total_weight,
662 matching: result_matching,
663 })
664}
665
666#[cfg(test)]
667mod tests {
668 use super::*;
669 use crate::algorithms::constructors::create::create;
670
671 fn make_k22() -> (Graph, Vec<bool>) {
672 let g = create(&[(0, 2), (0, 3), (1, 2), (1, 3)], 4, false).expect("K22");
673 let types = vec![false, false, true, true];
674 (g, types)
675 }
676
677 #[test]
680 fn is_matching_valid() {
681 let (g, types) = make_k22();
682 let m = vec![Some(2), Some(3), Some(0), Some(1)];
683 assert!(is_matching(&g, Some(&types), &m).expect("ok"));
684 }
685
686 #[test]
687 fn is_matching_wrong_length() {
688 let (g, _) = make_k22();
689 let m = vec![Some(1), Some(0)];
690 assert!(!is_matching(&g, None, &m).expect("ok"));
691 }
692
693 #[test]
694 fn is_matching_non_mutual() {
695 let (g, _) = make_k22();
696 let m = vec![Some(2), None, None, None];
697 assert!(!is_matching(&g, None, &m).expect("ok"));
698 }
699
700 #[test]
701 fn is_matching_no_edge() {
702 let g = create(&[(0, 1)], 3, false).expect("ok");
703 let m = vec![Some(2), None, Some(0)];
705 assert!(!is_matching(&g, None, &m).expect("ok"));
706 }
707
708 #[test]
709 fn is_matching_all_unmatched_with_types() {
710 let g = create(&[(0, 1), (1, 2), (2, 3)], 4, false).expect("ok");
711 let types = vec![false, true, false, true];
712 let m = vec![None, None, None, None];
713 assert!(is_matching(&g, Some(&types), &m).expect("ok"));
714 }
715
716 #[test]
717 fn is_matching_types_same_partition() {
718 let g = create(&[(0, 1), (1, 2)], 3, false).expect("ok");
719 let types = vec![false, false, true]; let m = vec![Some(1), Some(0), None];
721 assert!(!is_matching(&g, Some(&types), &m).expect("ok"));
722 }
723
724 #[test]
727 fn is_maximal_matching_true() {
728 let (g, types) = make_k22();
729 let m = vec![Some(2), Some(3), Some(0), Some(1)];
730 assert!(is_maximal_matching(&g, Some(&types), &m).expect("ok"));
731 }
732
733 #[test]
734 fn is_maximal_matching_false() {
735 let (g, types) = make_k22();
736 let m = vec![Some(2), None, Some(0), None];
738 assert!(!is_maximal_matching(&g, Some(&types), &m).expect("ok"));
739 }
740
741 #[test]
742 fn is_maximal_all_unmatched_no_edges() {
743 let g = Graph::new(3, false).expect("ok");
744 let m = vec![None, None, None];
745 assert!(is_maximal_matching(&g, None, &m).expect("ok"));
746 }
747
748 #[test]
751 fn max_matching_k22() {
752 let (g, types) = make_k22();
753 let r = maximum_bipartite_matching(&g, &types).expect("ok");
754 assert_eq!(r.matching_size, 2);
755 assert!(is_maximal_matching(&g, Some(&types), &r.matching).expect("ok"));
756 }
757
758 #[test]
759 fn max_matching_empty() {
760 let g = Graph::new(0, false).expect("ok");
761 let types: Vec<bool> = vec![];
762 let r = maximum_bipartite_matching(&g, &types).expect("ok");
763 assert_eq!(r.matching_size, 0);
764 }
765
766 #[test]
767 fn max_matching_singleton() {
768 let g = Graph::new(1, false).expect("ok");
769 let types = vec![false];
770 let r = maximum_bipartite_matching(&g, &types).expect("ok");
771 assert_eq!(r.matching_size, 0);
772 }
773
774 #[test]
775 fn max_matching_path_4() {
776 let g = create(&[(0, 1), (1, 2), (2, 3)], 4, false).expect("ok");
778 let types = vec![false, true, false, true];
779 let r = maximum_bipartite_matching(&g, &types).expect("ok");
780 assert_eq!(r.matching_size, 2);
781 assert!(is_maximal_matching(&g, Some(&types), &r.matching).expect("ok"));
782 }
783
784 #[test]
785 fn max_matching_star() {
786 let g = create(&[(0, 1), (0, 2), (0, 3), (0, 4)], 5, false).expect("ok");
788 let types = vec![false, true, true, true, true];
789 let r = maximum_bipartite_matching(&g, &types).expect("ok");
790 assert_eq!(r.matching_size, 1);
791 assert!(is_maximal_matching(&g, Some(&types), &r.matching).expect("ok"));
792 }
793
794 #[test]
795 fn max_matching_complete_bipartite_k33() {
796 let g = create(
797 &[
798 (0, 3),
799 (0, 4),
800 (0, 5),
801 (1, 3),
802 (1, 4),
803 (1, 5),
804 (2, 3),
805 (2, 4),
806 (2, 5),
807 ],
808 6,
809 false,
810 )
811 .expect("ok");
812 let types = vec![false, false, false, true, true, true];
813 let r = maximum_bipartite_matching(&g, &types).expect("ok");
814 assert_eq!(r.matching_size, 3);
815 assert!(is_maximal_matching(&g, Some(&types), &r.matching).expect("ok"));
816 }
817
818 #[test]
819 fn max_matching_not_bipartite_error() {
820 let g = create(&[(0, 1), (1, 2), (2, 0)], 3, false).expect("ok");
822 let types = vec![false, true, false]; let r = maximum_bipartite_matching(&g, &types);
824 assert!(r.is_err());
825 }
826
827 #[test]
828 fn max_matching_disconnected() {
829 let g = create(&[(0, 1), (2, 3)], 4, false).expect("ok");
831 let types = vec![false, true, false, true];
832 let r = maximum_bipartite_matching(&g, &types).expect("ok");
833 assert_eq!(r.matching_size, 2);
834 }
835
836 #[test]
837 fn max_matching_types_too_short() {
838 let g = create(&[(0, 1)], 2, false).expect("ok");
839 let types = vec![false];
840 let r = maximum_bipartite_matching(&g, &types);
841 assert!(r.is_err());
842 }
843
844 #[test]
847 fn weighted_matching_simple() {
848 let g = create(&[(0, 2), (0, 3), (1, 2), (1, 3)], 4, false).expect("ok");
850 let types = vec![false, false, true, true];
851 let weights = vec![1.0, 10.0, 10.0, 1.0];
852 let r = maximum_bipartite_matching_weighted(&g, &types, &weights, 0.0).expect("ok");
853 assert_eq!(r.matching_size, 2);
854 assert!((r.matching_weight - 20.0).abs() < 1e-9);
855 }
856
857 #[test]
858 fn weighted_matching_mit_notes() {
859 let g = create(
862 &[
863 (0, 6),
864 (0, 7),
865 (0, 8),
866 (0, 9),
867 (1, 5),
868 (1, 6),
869 (1, 7),
870 (1, 8),
871 (1, 9),
872 (2, 5),
873 (2, 6),
874 (2, 7),
875 (2, 8),
876 (2, 9),
877 (3, 5),
878 (3, 7),
879 (3, 9),
880 (4, 7),
881 ],
882 10,
883 false,
884 )
885 .expect("ok");
886 let types: Vec<bool> = (0..10).map(|i| i >= 5).collect();
887 let weights = vec![
888 2.0, 7.0, 2.0, 3.0, 1.0, 3.0, 9.0, 3.0, 3.0, 1.0, 3.0, 3.0, 1.0, 2.0, 4.0, 1.0, 2.0, 3.0, ];
894 let r = maximum_bipartite_matching_weighted(&g, &types, &weights, 0.0).expect("ok");
895 assert_eq!(r.matching_size, 4);
896 assert!((r.matching_weight - 19.0).abs() < 1e-9);
897 assert!(is_maximal_matching(&g, Some(&types), &r.matching).expect("ok"));
898 }
899
900 #[test]
901 fn weighted_matching_generated_case1() {
902 let g = create(&[(0, 8), (2, 7), (3, 7), (3, 8), (4, 5), (4, 9)], 10, false).expect("ok");
903 let types: Vec<bool> = (0..10).map(|i| i >= 5).collect();
904 let weights = vec![8.0, 5.0, 9.0, 18.0, 20.0, 13.0];
905 let r = maximum_bipartite_matching_weighted(&g, &types, &weights, 0.0).expect("ok");
906 assert!((r.matching_weight - 43.0).abs() < 1e-9);
907 }
908
909 #[test]
910 fn weighted_matching_generated_case2() {
911 let g = create(&[(0, 5), (0, 6), (1, 7), (2, 5), (3, 5), (3, 9)], 10, false).expect("ok");
912 let types: Vec<bool> = (0..10).map(|i| i >= 5).collect();
913 let weights = vec![20.0, 4.0, 20.0, 3.0, 13.0, 1.0];
914 let r = maximum_bipartite_matching_weighted(&g, &types, &weights, 0.0).expect("ok");
915 assert!((r.matching_weight - 41.0).abs() < 1e-9);
916 }
917
918 #[test]
919 fn weighted_matching_empty() {
920 let g = Graph::new(0, false).expect("ok");
921 let r = maximum_bipartite_matching_weighted(&g, &[], &[], 0.0).expect("ok");
922 assert_eq!(r.matching_size, 0);
923 }
924
925 #[test]
926 fn weighted_matching_no_edges() {
927 let g = Graph::new(4, false).expect("ok");
928 let types = vec![false, false, true, true];
929 let r = maximum_bipartite_matching_weighted(&g, &types, &[], 0.0).expect("ok");
930 assert_eq!(r.matching_size, 0);
931 }
932
933 #[cfg(all(test, feature = "proptest-harness"))]
936 mod proptests {
937 use super::*;
938 use proptest::prelude::*;
939
940 fn arb_bipartite_graph(
941 max_a: u32,
942 max_b: u32,
943 ) -> impl Strategy<Value = (Graph, Vec<bool>)> {
944 (1..=max_a, 1..=max_b).prop_flat_map(move |(a, b)| {
945 let pool = (a as usize) * (b as usize);
946 let mask_len = pool.min(20);
947 proptest::collection::vec(proptest::bool::ANY, mask_len).prop_map(move |mask| {
948 let n = a + b;
949 let mut edges = Vec::new();
950 for (idx, &present) in mask.iter().enumerate() {
951 if present {
952 let u = (idx as u32) / b;
953 let v = a + (idx as u32) % b;
954 edges.push((u, v));
955 }
956 }
957 let g = create(&edges, n, false).expect("bipartite graph");
958 let types: Vec<bool> = (0..n).map(|i| i >= a).collect();
959 (g, types)
960 })
961 })
962 }
963
964 proptest! {
965 #[test]
966 fn matching_is_valid((g, types) in arb_bipartite_graph(6, 6)) {
967 let r = maximum_bipartite_matching(&g, &types).expect("ok");
968 prop_assert!(is_matching(&g, Some(&types), &r.matching).expect("ok"));
969 prop_assert!(is_maximal_matching(&g, Some(&types), &r.matching).expect("ok"));
970 }
971
972 #[test]
973 fn matching_size_leq_min_partition(
974 (g, types) in arb_bipartite_graph(6, 6)
975 ) {
976 let r = maximum_bipartite_matching(&g, &types).expect("ok");
977 let a_size = types.iter().filter(|&&t| !t).count();
978 let b_size = types.iter().filter(|&&t| t).count();
979 prop_assert!(r.matching_size <= a_size.min(b_size));
980 }
981
982 #[test]
983 fn matching_size_leq_ecount(
984 (g, types) in arb_bipartite_graph(6, 6)
985 ) {
986 let r = maximum_bipartite_matching(&g, &types).expect("ok");
987 prop_assert!(r.matching_size <= g.ecount());
988 }
989
990 #[test]
991 fn weighted_matching_is_valid((g, types) in arb_bipartite_graph(5, 5)) {
992 let ne = g.ecount();
993 let weights: Vec<f64> = (0..ne).map(|i| (i as f64) + 1.0).collect();
994 if ne > 0 {
995 let r = maximum_bipartite_matching_weighted(&g, &types, &weights, 0.0).expect("ok");
996 prop_assert!(is_matching(&g, Some(&types), &r.matching).expect("ok"));
997 }
998 }
999
1000 #[test]
1001 fn weighted_geq_unweighted_unit(
1002 (g, types) in arb_bipartite_graph(5, 5)
1003 ) {
1004 let unw = maximum_bipartite_matching(&g, &types).expect("ok");
1005 let ne = g.ecount();
1006 let weights: Vec<f64> = vec![1.0; ne];
1007 if ne > 0 {
1008 let w = maximum_bipartite_matching_weighted(&g, &types, &weights, 0.0).expect("ok");
1009 prop_assert!(w.matching_size >= unw.matching_size.saturating_sub(1),
1011 "weighted: {}, unweighted: {}", w.matching_size, unw.matching_size);
1012 }
1013 }
1014 }
1015 }
1016}