1use std::collections::VecDeque;
11
12use crate::incidence::EqualityIncidence;
13
14const NIL: usize = usize::MAX;
15const INF: usize = usize::MAX;
16
17#[derive(Debug, Clone, Default)]
20pub struct BipartiteMatching {
21 pub row_to_var: Vec<Option<usize>>,
24 pub var_to_row: Vec<Option<usize>>,
26 pub size: usize,
28}
29
30pub fn hopcroft_karp(inc: &EqualityIncidence) -> BipartiteMatching {
58 let n_rows = inc.n_eq_rows();
59 let n_vars = inc.n_vars;
60
61 let mut pair_u: Vec<usize> = vec![NIL; n_rows];
63 let mut pair_v: Vec<usize> = vec![NIL; n_vars];
64 let mut dist: Vec<usize> = vec![INF; n_rows];
66
67 let mut size = 0;
68 let max_rounds = (n_rows.min(n_vars) + 1) * 2;
73 let mut round = 0;
74 while bfs(inc, &pair_u, &pair_v, &mut dist) {
75 let mut augmented = false;
76 for r in 0..n_rows {
77 if pair_u[r] == NIL && dfs(inc, r, &mut pair_u, &mut pair_v, &mut dist) {
78 size += 1;
79 augmented = true;
80 }
81 }
82 if !augmented {
83 break;
87 }
88 round += 1;
89 debug_assert!(round <= max_rounds, "Hopcroft-Karp round cap exceeded");
90 }
91
92 let row_to_var = pair_u
93 .iter()
94 .map(|&v| if v == NIL { None } else { Some(v) })
95 .collect();
96 let var_to_row = pair_v
97 .iter()
98 .map(|&r| if r == NIL { None } else { Some(r) })
99 .collect();
100
101 BipartiteMatching {
102 row_to_var,
103 var_to_row,
104 size,
105 }
106}
107
108fn bfs(inc: &EqualityIncidence, pair_u: &[usize], pair_v: &[usize], dist: &mut [usize]) -> bool {
113 let mut queue: VecDeque<usize> = VecDeque::new();
114 for r in 0..inc.n_eq_rows() {
115 if pair_u[r] == NIL {
116 dist[r] = 0;
117 queue.push_back(r);
118 } else {
119 dist[r] = INF;
120 }
121 }
122 let mut dist_nil = INF;
126 while let Some(r) = queue.pop_front() {
127 if dist[r] >= dist_nil {
128 continue;
129 }
130 for &v in inc.neighbors(r) {
131 let next = pair_v[v];
132 if next == NIL {
133 if dist_nil == INF {
137 dist_nil = dist[r] + 1;
138 }
139 } else if dist[next] == INF {
140 dist[next] = dist[r] + 1;
141 queue.push_back(next);
142 }
143 }
144 }
145 dist_nil != INF
146}
147
148fn dfs(
151 inc: &EqualityIncidence,
152 r: usize,
153 pair_u: &mut [usize],
154 pair_v: &mut [usize],
155 dist: &mut [usize],
156) -> bool {
157 for &v in inc.neighbors(r) {
158 let next = pair_v[v];
159 let ok = if next == NIL {
160 true
161 } else if dist[next] == dist[r] + 1 {
162 dfs(inc, next, pair_u, pair_v, dist)
163 } else {
164 false
165 };
166 if ok {
167 pair_u[r] = v;
168 pair_v[v] = r;
169 return true;
170 }
171 }
172 dist[r] = INF;
173 false
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use crate::incidence::ProbeView;
180 use pounce_common::types::{Index, Number};
181
182 fn eq_inc(n_vars: usize, n_rows: usize, edges: &[(usize, usize)]) -> EqualityIncidence {
186 let mut per_row: Vec<Vec<usize>> = vec![Vec::new(); n_rows];
187 for &(r, v) in edges {
188 per_row[r].push(v);
189 }
190 let mut adj_ptr = Vec::with_capacity(n_rows + 1);
191 let mut vars = Vec::new();
192 adj_ptr.push(0);
193 for row in per_row.iter_mut() {
194 row.sort_unstable();
195 row.dedup();
196 vars.extend_from_slice(row);
197 adj_ptr.push(vars.len());
198 }
199 EqualityIncidence {
200 n_vars,
201 eq_row_inner_idx: (0..n_rows).collect(),
202 adj_ptr,
203 vars,
204 }
205 }
206
207 #[test]
208 fn square_full_match_3x3() {
209 let inc = eq_inc(3, 3, &[(0, 0), (1, 1), (2, 2)]);
212 let m = hopcroft_karp(&inc);
213 assert_eq!(m.size, 3);
214 assert_eq!(m.row_to_var, vec![Some(0), Some(1), Some(2)]);
215 }
216
217 #[test]
218 fn under_determined_2x3() {
219 let inc = eq_inc(3, 2, &[(0, 0), (0, 1), (1, 1), (1, 2)]);
221 let m = hopcroft_karp(&inc);
222 assert_eq!(m.size, 2);
223 assert!(m.row_to_var.iter().all(|v| v.is_some()));
224 }
225
226 #[test]
227 fn over_determined_3x2() {
228 let inc = eq_inc(2, 3, &[(0, 0), (1, 1), (2, 0), (2, 1)]);
230 let m = hopcroft_karp(&inc);
231 assert_eq!(m.size, 2);
232 assert_eq!(m.row_to_var.iter().filter(|v| v.is_some()).count(), 2);
233 }
234
235 #[test]
236 fn disconnected_components() {
237 let inc = eq_inc(2, 2, &[(0, 0), (1, 1)]);
239 let m = hopcroft_karp(&inc);
240 assert_eq!(m.size, 2);
241 assert_eq!(m.row_to_var, vec![Some(0), Some(1)]);
242 }
243
244 #[test]
245 fn augmenting_path_required() {
246 let inc = eq_inc(2, 2, &[(0, 0), (0, 1), (1, 0)]);
250 let m = hopcroft_karp(&inc);
251 assert_eq!(m.size, 2);
252 }
253
254 #[test]
255 fn empty_graph_yields_empty_matching() {
256 let inc = eq_inc(0, 0, &[]);
257 let m = hopcroft_karp(&inc);
258 assert_eq!(m.size, 0);
259 assert!(m.row_to_var.is_empty());
260 assert!(m.var_to_row.is_empty());
261 }
262
263 #[test]
264 fn matches_built_via_probeview_too() {
265 let irow: [Index; 3] = [0, 0, 1];
268 let jcol: [Index; 3] = [0, 1, 0];
269 let g: [Number; 2] = [0.0, 0.0];
270 let p = ProbeView {
271 n_vars: 2,
272 m_rows: 2,
273 jac_irow: &irow,
274 jac_jcol: &jcol,
275 jac_values: None,
276 g_l: &g,
277 g_u: &g,
278 linearity: None,
279 one_based: false,
280 eq_tol: 1e-12,
281 excluded_vars: None,
282 excluded_rows: None,
283 };
284 let inc = EqualityIncidence::from_probe(&p);
285 let m = hopcroft_karp(&inc);
286 assert_eq!(m.size, 2);
287 }
288
289 fn brute_force_max_matching(edges: &[(usize, usize)], n_rows: usize, n_vars: usize) -> usize {
293 let e = edges.len();
294 let mut row_used = vec![false; n_rows];
295 let mut var_used = vec![false; n_vars];
296 let mut best = 0;
297 for mask in 0u32..(1u32 << e) {
298 row_used.iter_mut().for_each(|v| *v = false);
299 var_used.iter_mut().for_each(|v| *v = false);
300 let mut count = 0;
301 let mut valid = true;
302 for k in 0..e {
303 if (mask >> k) & 1 == 1 {
304 let (r, v) = edges[k];
305 if row_used[r] || var_used[v] {
306 valid = false;
307 break;
308 }
309 row_used[r] = true;
310 var_used[v] = true;
311 count += 1;
312 }
313 }
314 if valid && count > best {
315 best = count;
316 }
317 }
318 best
319 }
320
321 #[test]
329 fn konigs_theorem_random_check() {
330 let mut state: u64 = 0xdead_beef_cafe_f00d;
337 let mut next = || -> u64 {
338 state = state
339 .wrapping_mul(6364136223846793005)
340 .wrapping_add(1442695040888963407);
341 state >> 32
342 };
343
344 for _ in 0..20 {
345 let n_rows = 1 + (next() % 4) as usize; let n_vars = 1 + (next() % 4) as usize; let max_edges = (n_rows * n_vars).min(8); let n_edges = (next() % (max_edges as u64 + 1)) as usize;
349
350 let mut edges_set = std::collections::BTreeSet::<(usize, usize)>::new();
354 let mut draws = 0usize;
355 while edges_set.len() < n_edges {
356 let r = (next() % n_rows as u64) as usize;
357 let v = (next() % n_vars as u64) as usize;
358 edges_set.insert((r, v));
359 draws += 1;
360 assert!(draws < 10_000, "edge draw loop is not making progress");
361 }
362 let edges: Vec<(usize, usize)> = edges_set.into_iter().collect();
363
364 let inc = eq_inc(n_vars, n_rows, &edges);
365 let hk = hopcroft_karp(&inc).size;
366 let bf = brute_force_max_matching(&edges, n_rows, n_vars);
367 assert_eq!(
368 hk, bf,
369 "Hopcroft-Karp disagrees with brute force on {edges:?} ({n_rows}x{n_vars})"
370 );
371 }
372 }
373}