1use crate::algorithms::connectivity::is_bipartite;
7use crate::base::{EdgeWeight, Graph, IndexType, Node};
8use crate::error::{GraphError, Result};
9use std::collections::{HashMap, HashSet};
10use std::hash::Hash;
11
12#[derive(Debug, Clone)]
14pub struct BipartiteMatching<N: Node> {
15 pub matching: HashMap<N, N>,
17 pub size: usize,
19}
20
21#[allow(dead_code)]
32pub fn maximum_bipartite_matching<N, E, Ix>(
33 graph: &Graph<N, E, Ix>,
34 coloring: &HashMap<N, u8>,
35) -> BipartiteMatching<N>
36where
37 N: Node + std::fmt::Debug,
38 E: EdgeWeight,
39 Ix: petgraph::graph::IndexType,
40{
41 let mut node_to_idx: HashMap<N, petgraph::graph::NodeIndex<Ix>> = HashMap::new();
43 for node_idx in graph.inner().node_indices() {
44 node_to_idx.insert(graph.inner()[node_idx].clone(), node_idx);
45 }
46
47 let mut left_nodes = Vec::new();
49 let mut right_nodes = Vec::new();
50
51 for (node, &color) in coloring {
52 if color == 0 {
53 left_nodes.push(node.clone());
54 } else {
55 right_nodes.push(node.clone());
56 }
57 }
58
59 let mut matching: HashMap<N, N> = HashMap::new();
61 let mut reverse_matching: HashMap<N, N> = HashMap::new();
62
63 for left_node in &left_nodes {
65 if !matching.contains_key(left_node) {
66 let mut visited = HashSet::new();
67 augment_path(
68 graph,
69 left_node,
70 &mut matching,
71 &mut reverse_matching,
72 &mut visited,
73 coloring,
74 );
75 }
76 }
77
78 BipartiteMatching {
79 size: matching.len(),
80 matching,
81 }
82}
83
84#[allow(dead_code)]
86fn augment_path<N, E, Ix>(
87 graph: &Graph<N, E, Ix>,
88 node: &N,
89 matching: &mut HashMap<N, N>,
90 reverse_matching: &mut HashMap<N, N>,
91 visited: &mut HashSet<N>,
92 coloring: &HashMap<N, u8>,
93) -> bool
94where
95 N: Node + std::fmt::Debug,
96 E: EdgeWeight,
97 Ix: petgraph::graph::IndexType,
98{
99 visited.insert(node.clone());
101
102 if let Ok(neighbors) = graph.neighbors(node) {
104 for neighbor in neighbors {
105 if coloring.get(node) == coloring.get(&neighbor) {
107 continue;
108 }
109
110 if let std::collections::hash_map::Entry::Vacant(e) =
112 reverse_matching.entry(neighbor.clone())
113 {
114 matching.insert(node.clone(), neighbor.clone());
115 e.insert(node.clone());
116 return true;
117 }
118
119 let matched_node = reverse_matching[&neighbor].clone();
121 if !visited.contains(&matched_node)
122 && augment_path(
123 graph,
124 &matched_node,
125 matching,
126 reverse_matching,
127 visited,
128 coloring,
129 )
130 {
131 matching.insert(node.clone(), neighbor.clone());
132 reverse_matching.insert(neighbor, node.clone());
133 return true;
134 }
135 }
136 }
137
138 false
139}
140
141#[allow(dead_code)]
149pub fn minimum_weight_bipartite_matching<N, E, Ix>(
150 graph: &Graph<N, E, Ix>,
151) -> Result<(f64, Vec<(N, N)>)>
152where
153 N: Node + Clone + Hash + Eq + std::fmt::Debug,
154 E: EdgeWeight + Into<f64> + Clone,
155 Ix: IndexType,
156{
157 let bipartite_result = is_bipartite(graph);
159
160 if !bipartite_result.is_bipartite {
161 return Err(GraphError::InvalidGraph(
162 "Graph is not bipartite".to_string(),
163 ));
164 }
165
166 let coloring = bipartite_result.coloring;
167
168 let mut left_nodes = Vec::new();
170 let mut right_nodes = Vec::new();
171
172 for (node, &color) in &coloring {
173 if color == 0 {
174 left_nodes.push(node.clone());
175 } else {
176 right_nodes.push(node.clone());
177 }
178 }
179
180 let n_left = left_nodes.len();
181 let n_right = right_nodes.len();
182
183 if n_left != n_right {
184 return Err(GraphError::InvalidGraph(
185 "Bipartite graph must have equal number of nodes in each partition for perfect matching".to_string()
186 ));
187 }
188
189 if n_left == 0 {
190 return Ok((0.0, vec![]));
191 }
192
193 let mut cost_matrix = vec![vec![f64::INFINITY; n_right]; n_left];
195
196 for (i, left_node) in left_nodes.iter().enumerate() {
197 for (j, right_node) in right_nodes.iter().enumerate() {
198 if let Ok(weight) = graph.edge_weight(left_node, right_node) {
199 cost_matrix[i][j] = weight.into();
200 }
201 }
202 }
203
204 let assignment = hungarian_algorithm(&cost_matrix).map_err(|e| {
209 GraphError::InvalidGraph(format!(
210 "minimum_weight_bipartite_matching: {e} (graph may be missing edges needed for a perfect matching)"
211 ))
212 })?;
213
214 let mut total_cost = 0.0;
215 let mut matching = Vec::with_capacity(n_left);
216 for (j, &row_1indexed) in assignment.iter().enumerate().skip(1) {
217 let i = row_1indexed - 1;
218 let cost = cost_matrix[i][j - 1];
219 if !cost.is_finite() {
220 return Err(GraphError::InvalidGraph(
221 "minimum_weight_bipartite_matching: no perfect matching exists using only real edges".to_string(),
222 ));
223 }
224 total_cost += cost;
225 matching.push((left_nodes[i].clone(), right_nodes[j - 1].clone()));
226 }
227
228 Ok((total_cost, matching))
229}
230
231#[allow(clippy::needless_range_loop)]
245fn hungarian_algorithm(cost: &[Vec<f64>]) -> std::result::Result<Vec<usize>, String> {
246 let n = cost.len();
247 if n == 0 {
248 return Ok(vec![0]);
249 }
250
251 let mut u = vec![0.0_f64; n + 1];
255 let mut v = vec![0.0_f64; n + 1];
256 let mut p = vec![0usize; n + 1]; let mut way = vec![0usize; n + 1];
258
259 for i in 1..=n {
260 p[0] = i;
261 let mut j0 = 0usize;
262 let mut minv = vec![f64::INFINITY; n + 1];
263 let mut used = vec![false; n + 1];
264
265 loop {
266 used[j0] = true;
267 let i0 = p[j0];
268 let mut delta = f64::INFINITY;
269 let mut j1 = 0usize;
270
271 for j in 1..=n {
272 if !used[j] {
273 let cur = cost[i0 - 1][j - 1] - u[i0] - v[j];
274 if cur < minv[j] {
275 minv[j] = cur;
276 way[j] = j0;
277 }
278 if minv[j] < delta {
279 delta = minv[j];
280 j1 = j;
281 }
282 }
283 }
284
285 if !delta.is_finite() {
286 return Err("no feasible perfect matching exists".to_string());
290 }
291
292 for j in 0..=n {
293 if used[j] {
294 u[p[j]] += delta;
295 v[j] -= delta;
296 } else {
297 minv[j] -= delta;
298 }
299 }
300
301 j0 = j1;
302 if p[j0] == 0 {
303 break;
304 }
305 }
306
307 loop {
308 let j1 = way[j0];
309 p[j0] = p[j1];
310 j0 = j1;
311 if j0 == 0 {
312 break;
313 }
314 }
315 }
316
317 Ok(p)
318}
319
320#[allow(dead_code)]
321fn minimum_weight_matching_bruteforce<N>(
322 left_nodes: &[N],
323 right_nodes: &[N],
324 cost_matrix: &[Vec<f64>],
325) -> Result<(f64, Vec<(N, N)>)>
326where
327 N: Node + Clone + std::fmt::Debug,
328{
329 let n = left_nodes.len();
330 let mut best_cost = f64::INFINITY;
331 let mut best_matching = Vec::new();
332
333 let mut perm: Vec<usize> = (0..n).collect();
335
336 loop {
337 let mut cost = 0.0;
339 for i in 0..n {
340 cost += cost_matrix[i][perm[i]];
341 }
342
343 if cost < best_cost {
344 best_cost = cost;
345 best_matching = (0..n)
346 .map(|i| (left_nodes[i].clone(), right_nodes[perm[i]].clone()))
347 .collect();
348 }
349
350 if !next_permutation(&mut perm) {
352 break;
353 }
354 }
355
356 Ok((best_cost, best_matching))
357}
358
359#[allow(dead_code)]
360fn next_permutation(perm: &mut [usize]) -> bool {
361 let n = perm.len();
362
363 let mut k = None;
365 for i in 0..n - 1 {
366 if perm[i] < perm[i + 1] {
367 k = Some(i);
368 }
369 }
370
371 let k = match k {
372 Some(k) => k,
373 None => return false, };
375
376 let mut l = k + 1;
378 for i in k + 1..n {
379 if perm[k] < perm[i] {
380 l = i;
381 }
382 }
383
384 perm.swap(k, l);
386
387 perm[k + 1..].reverse();
389
390 true
391}
392
393#[derive(Debug, Clone)]
395pub struct MaximumMatching<N: Node> {
396 pub matching: Vec<(N, N)>,
398 pub size: usize,
400}
401
402#[allow(dead_code)]
413pub fn maximum_cardinality_matching<N, E, Ix>(graph: &Graph<N, E, Ix>) -> MaximumMatching<N>
414where
415 N: Node + Clone + std::fmt::Debug,
416 E: EdgeWeight,
417 Ix: IndexType,
418{
419 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
420 let n = nodes.len();
421
422 if n == 0 {
423 return MaximumMatching {
424 matching: Vec::new(),
425 size: 0,
426 };
427 }
428
429 let mut matching = Vec::new();
432 let mut matched = vec![false; n];
433 let node_to_idx: HashMap<N, usize> = nodes
434 .iter()
435 .enumerate()
436 .map(|(i, n)| (n.clone(), i))
437 .collect();
438
439 for (i, node) in nodes.iter().enumerate() {
441 if matched[i] {
442 continue;
443 }
444
445 if let Ok(neighbors) = graph.neighbors(node) {
446 for neighbor in neighbors {
447 if let Some(&j) = node_to_idx.get(&neighbor) {
448 if !matched[j] {
449 matching.push((node.clone(), neighbor));
451 matched[i] = true;
452 matched[j] = true;
453 break;
454 }
455 }
456 }
457 }
458 }
459
460 MaximumMatching {
461 size: matching.len(),
462 matching,
463 }
464}
465
466#[allow(dead_code)]
477pub fn maximal_matching<N, E, Ix>(graph: &Graph<N, E, Ix>) -> MaximumMatching<N>
478where
479 N: Node + Clone + std::fmt::Debug,
480 E: EdgeWeight,
481 Ix: IndexType,
482{
483 let mut matching = Vec::new();
484 let mut matched_nodes = HashSet::new();
485
486 let edges = graph.edges();
488
489 for edge in edges {
491 if !matched_nodes.contains(&edge.source) && !matched_nodes.contains(&edge.target) {
492 matching.push((edge.source.clone(), edge.target.clone()));
493 matched_nodes.insert(edge.source);
494 matched_nodes.insert(edge.target);
495 }
496 }
497
498 MaximumMatching {
499 size: matching.len(),
500 matching,
501 }
502}
503
504#[allow(dead_code)]
516pub fn stable_marriage(
517 left_prefs: &[Vec<usize>],
518 right_prefs: &[Vec<usize>],
519) -> Result<Vec<(usize, usize)>> {
520 let n = left_prefs.len();
521
522 if n != right_prefs.len() {
523 return Err(GraphError::InvalidGraph(
524 "Left and right sets must have equal size".to_string(),
525 ));
526 }
527
528 if n == 0 {
529 return Ok(Vec::new());
530 }
531
532 for (i, prefs) in left_prefs.iter().enumerate() {
534 if prefs.len() != n {
535 return Err(GraphError::InvalidGraph(format!(
536 "Left preference list {i} has wrong length"
537 )));
538 }
539 let mut sorted_prefs = prefs.clone();
540 sorted_prefs.sort_unstable();
541 if sorted_prefs != (0..n).collect::<Vec<_>>() {
542 return Err(GraphError::InvalidGraph(format!(
543 "Left preference list {i} is not a valid permutation"
544 )));
545 }
546 }
547
548 for (i, prefs) in right_prefs.iter().enumerate() {
549 if prefs.len() != n {
550 return Err(GraphError::InvalidGraph(format!(
551 "Right preference list {i} has wrong length"
552 )));
553 }
554 let mut sorted_prefs = prefs.clone();
555 sorted_prefs.sort_unstable();
556 if sorted_prefs != (0..n).collect::<Vec<_>>() {
557 return Err(GraphError::InvalidGraph(format!(
558 "Right preference list {i} is not a valid permutation"
559 )));
560 }
561 }
562
563 let mut right_inv_prefs = vec![vec![0; n]; n];
565 for (i, prefs) in right_prefs.iter().enumerate() {
566 for (rank, &person) in prefs.iter().enumerate() {
567 right_inv_prefs[i][person] = rank;
568 }
569 }
570
571 let mut left_partner = vec![None; n];
573 let mut right_partner = vec![None; n];
574 let mut left_next_proposal = vec![0; n];
575 let mut free_left: std::collections::VecDeque<usize> = (0..n).collect();
576
577 while let Some(left) = free_left.pop_front() {
578 if left_next_proposal[left] >= n {
579 continue; }
581
582 let right = left_prefs[left][left_next_proposal[left]];
583 left_next_proposal[left] += 1;
584
585 match right_partner[right] {
586 None => {
587 left_partner[left] = Some(right);
589 right_partner[right] = Some(left);
590 }
591 Some(current_left) => {
592 if right_inv_prefs[right][left] < right_inv_prefs[right][current_left] {
594 left_partner[left] = Some(right);
596 right_partner[right] = Some(left);
597 left_partner[current_left] = None;
598 free_left.push_back(current_left);
599 } else {
600 free_left.push_back(left);
602 }
603 }
604 }
605 }
606
607 let mut result = Vec::new();
609 for (left, partner) in left_partner.iter().enumerate() {
610 if let Some(right) = partner {
611 result.push((left, *right));
612 }
613 }
614
615 Ok(result)
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621 use crate::error::Result as GraphResult;
622 use crate::generators::create_graph;
623
624 #[test]
625 fn test_maximum_bipartite_matching() -> GraphResult<()> {
626 let mut graph = create_graph::<&str, ()>();
627
628 graph.add_edge("A", "1", ())?;
630 graph.add_edge("A", "2", ())?;
631 graph.add_edge("B", "2", ())?;
632 graph.add_edge("B", "3", ())?;
633 graph.add_edge("C", "3", ())?;
634
635 let mut coloring = HashMap::new();
637 coloring.insert("A", 0);
638 coloring.insert("B", 0);
639 coloring.insert("C", 0);
640 coloring.insert("1", 1);
641 coloring.insert("2", 1);
642 coloring.insert("3", 1);
643
644 let matching = maximum_bipartite_matching(&graph, &coloring);
645
646 assert_eq!(matching.size, 3);
648
649 let mut used_right = HashSet::new();
651 for right in matching.matching.values() {
652 assert!(!used_right.contains(right));
653 used_right.insert(right);
654 }
655
656 Ok(())
657 }
658
659 #[test]
660 fn test_minimum_weight_bipartite_matching() -> GraphResult<()> {
661 let mut graph = create_graph::<&str, f64>();
662
663 graph.add_edge("A", "1", 1.0)?;
665 graph.add_edge("A", "2", 3.0)?;
666 graph.add_edge("B", "1", 2.0)?;
667 graph.add_edge("B", "2", 1.0)?;
668
669 let (total_weight, matching) = minimum_weight_bipartite_matching(&graph)?;
670
671 assert_eq!(total_weight, 2.0);
673 assert_eq!(matching.len(), 2);
674
675 Ok(())
676 }
677
678 #[test]
679 fn test_hungarian_matches_bruteforce_on_random_instances() {
680 let mut state: u64 = 0x1234_5678_9abc_def0;
685
686 for n in 1..=6usize {
687 for trial in 0..20u32 {
688 let mut cost_matrix = vec![vec![0.0_f64; n]; n];
689 for row in cost_matrix.iter_mut() {
690 for cell in row.iter_mut() {
691 state = state
692 .wrapping_mul(6364136223846793005)
693 .wrapping_add(1442695040888963407);
694 *cell = ((state >> 11) as f64 / (1u64 << 53) as f64) * 100.0;
695 }
696 }
697
698 let left_nodes: Vec<usize> = (0..n).collect();
699 let right_nodes: Vec<usize> = (0..n).collect();
700
701 let (bruteforce_cost, _) =
702 minimum_weight_matching_bruteforce(&left_nodes, &right_nodes, &cost_matrix)
703 .expect("bruteforce failed");
704 let assignment = hungarian_algorithm(&cost_matrix).expect("hungarian failed");
705 let hungarian_cost: f64 =
706 (1..=n).map(|j| cost_matrix[assignment[j] - 1][j - 1]).sum();
707
708 assert!(
709 (bruteforce_cost - hungarian_cost).abs() < 1e-6,
710 "n={n} trial={trial}: hungarian cost {hungarian_cost} should match bruteforce {bruteforce_cost}"
711 );
712 }
713 }
714 }
715
716 #[test]
717 fn test_minimum_weight_bipartite_matching_large_finds_true_optimum() {
718 let mut graph = create_graph::<i32, f64>();
727 graph.add_edge(0, 10, 1.0).expect("Operation failed");
728 graph.add_edge(0, 11, 2.0).expect("Operation failed");
729 graph.add_edge(1, 10, 1.0).expect("Operation failed");
730 graph.add_edge(1, 11, 3.0).expect("Operation failed");
731 graph.add_edge(2, 12, 0.0).expect("Operation failed");
732 graph.add_edge(3, 13, 0.0).expect("Operation failed");
733 graph.add_edge(4, 14, 0.0).expect("Operation failed");
734 graph.add_edge(5, 15, 0.0).expect("Operation failed");
735 graph.add_edge(6, 16, 0.0).expect("Operation failed");
736
737 let (total_weight, matching) =
738 minimum_weight_bipartite_matching(&graph).expect("matching failed");
739
740 assert_eq!(matching.len(), 7);
741 assert!(
742 (total_weight - 3.0).abs() < 1e-9,
743 "expected the true optimum 3.0 (not the greedy-suboptimal 4.0), got {total_weight}"
744 );
745 }
746
747 #[test]
748 fn test_minimum_weight_bipartite_matching_infeasible_returns_error() {
749 let mut graph = create_graph::<i32, f64>();
753 graph.add_edge(0, 10, 1.0).expect("Operation failed");
754 graph.add_node(1);
755 graph.add_edge(2, 11, 1.0).expect("Operation failed");
756
757 assert!(minimum_weight_bipartite_matching(&graph).is_err());
758 }
759
760 #[test]
761 fn test_hungarian_algorithm_detects_infeasible_instance() {
762 let inf = f64::INFINITY;
769 let cost_matrix = vec![
770 vec![1.0, inf, inf],
771 vec![1.0, inf, inf],
772 vec![inf, 1.0, 1.0],
773 ];
774
775 assert!(hungarian_algorithm(&cost_matrix).is_err());
776 }
777
778 #[test]
779 fn test_maximum_cardinality_matching() {
780 let mut graph = create_graph::<&str, ()>();
781
782 graph.add_edge("A", "B", ()).expect("Operation failed");
784 graph.add_edge("C", "D", ()).expect("Operation failed");
785 graph.add_edge("E", "F", ()).expect("Operation failed");
786
787 let matching = maximum_cardinality_matching(&graph);
788
789 assert_eq!(matching.size, 3);
791 assert_eq!(matching.matching.len(), 3);
792
793 let mut matched_nodes = HashSet::new();
795 for (u, v) in &matching.matching {
796 assert!(!matched_nodes.contains(u));
797 assert!(!matched_nodes.contains(v));
798 matched_nodes.insert(u);
799 matched_nodes.insert(v);
800 }
801 }
802
803 #[test]
804 fn test_maximal_matching() {
805 let mut graph = create_graph::<i32, ()>();
806
807 graph.add_edge(1, 2, ()).expect("Operation failed");
809 graph.add_edge(2, 3, ()).expect("Operation failed");
810 graph.add_edge(3, 1, ()).expect("Operation failed");
811
812 let matching = maximal_matching(&graph);
813
814 assert_eq!(matching.size, 1);
816 assert_eq!(matching.matching.len(), 1);
817
818 let mut matched_nodes = HashSet::new();
820 for (u, v) in &matching.matching {
821 assert!(!matched_nodes.contains(u));
822 assert!(!matched_nodes.contains(v));
823 matched_nodes.insert(u);
824 matched_nodes.insert(v);
825 }
826 }
827
828 #[test]
829 fn test_stable_marriage() -> GraphResult<()> {
830 let left_prefs = vec![
832 vec![0, 1, 2], vec![1, 0, 2], vec![0, 1, 2], ];
836
837 let right_prefs = vec![
838 vec![2, 1, 0], vec![0, 2, 1], vec![0, 1, 2], ];
842
843 let matching = stable_marriage(&left_prefs, &right_prefs)?;
844
845 assert_eq!(matching.len(), 3);
847
848 let mut matched_left = HashSet::new();
850 let mut matched_right = HashSet::new();
851 for (left, right) in &matching {
852 assert!(!matched_left.contains(left));
853 assert!(!matched_right.contains(right));
854 matched_left.insert(*left);
855 matched_right.insert(*right);
856 }
857
858 Ok(())
859 }
860
861 #[test]
862 fn test_stable_marriage_empty() -> GraphResult<()> {
863 let left_prefs: Vec<Vec<usize>> = vec![];
864 let right_prefs: Vec<Vec<usize>> = vec![];
865
866 let matching = stable_marriage(&left_prefs, &right_prefs)?;
867 assert_eq!(matching.len(), 0);
868
869 Ok(())
870 }
871
872 #[test]
873 fn test_stable_marriage_invalid_input() {
874 let left_prefs = vec![vec![0]];
876 let right_prefs = vec![vec![0], vec![1]];
877
878 assert!(stable_marriage(&left_prefs, &right_prefs).is_err());
879
880 let left_prefs = vec![vec![0, 0]]; let right_prefs = vec![vec![0, 1]];
883
884 assert!(stable_marriage(&left_prefs, &right_prefs).is_err());
885 }
886}