|
16 | 16 | //! |
17 | 17 | //! Solving Minimum Matrix Domination on the constructed instance yields a |
18 | 18 | //! minimum edge dominating set of `B`, which is in general NOT a matching. |
19 | | -//! Yannakakis and Gavril (1980) prove that any edge dominating set can be |
| 19 | +//! Yannakakis and Gavril (1980) prove that any edge dominating set `D` can be |
20 | 20 | //! transformed in polynomial time into an independent edge dominating set |
21 | | -//! (a maximal matching) of the same size. We implement this conversion by a |
22 | | -//! direct search: enumerate maximal matchings of `B` and return one of size at |
23 | | -//! most `|EDS|`. Because every minimum maximal matching is also an EDS and the |
24 | | -//! two minima are equal, such a matching always exists when the target witness |
25 | | -//! is optimal. |
| 21 | +//! (a maximal matching) `M` of the same or smaller size. We implement this |
| 22 | +//! polynomial transformation directly: repeatedly resolve adjacent pairs in |
| 23 | +//! `D` by either dropping a redundant edge (when its endpoint is already |
| 24 | +//! dominated by `D \ {e}`) or swapping it for an edge whose new endpoint lies |
| 25 | +//! outside the current vertex cover. The procedure runs in `O(|F|^3)` worst |
| 26 | +//! case and never enumerates configurations. |
26 | 27 | //! |
27 | 28 | //! ## Source variant |
28 | 29 | //! |
@@ -60,51 +61,229 @@ impl ReductionResult for ReductionMMMToMatrixDomination { |
60 | 61 | } |
61 | 62 |
|
62 | 63 | /// Extract a maximal matching of the source bipartite graph from a |
63 | | - /// matrix-domination witness. |
| 64 | + /// matrix-domination witness via the Yannakakis-Gavril (1980) polynomial |
| 65 | + /// EDS-to-IEDS transformation. |
64 | 66 | /// |
65 | 67 | /// The target witness identifies a set of 1-entries of `M`. Each selected |
66 | 68 | /// 1-entry in the upper-right block `B*` corresponds bijectively to a |
67 | 69 | /// source edge, so the selection induces an edge set `D` of `B` that is an |
68 | | - /// edge dominating set. The minimum edge dominating set size of a graph |
69 | | - /// equals the minimum maximal matching size [Yannakakis-Gavril 1980], so |
70 | | - /// any optimal target witness yields `|D|` equal to `mm(B)`. We then |
71 | | - /// recover a maximal matching `M` of `B` with `|M| <= |D|` by enumerating |
72 | | - /// candidate source configurations. |
| 70 | + /// edge dominating set (EDS). Arbitrary optimal MMD witnesses may select |
| 71 | + /// 1-entries whose corresponding source edges form a connected subgraph |
| 72 | + /// rather than a matching (e.g. two edges sharing a left endpoint), so |
| 73 | + /// `D` is not in general independent. |
| 74 | + /// |
| 75 | + /// The Yannakakis-Gavril transformation (Theorem 1 of @yannakakis1980) |
| 76 | + /// converts any EDS into an independent EDS (a maximal matching) of the |
| 77 | + /// same or smaller size by repeatedly applying one of the following |
| 78 | + /// reductions while `D` contains two adjacent edges `e1 = (u, v)` and |
| 79 | + /// `e2 = (v, w)`: |
| 80 | + /// |
| 81 | + /// - **Drop:** if every edge of `B` incident to `u` is already dominated |
| 82 | + /// by `D \ {e1}`, set `D := D \ {e1}` (size strictly decreases). |
| 83 | + /// Symmetric for `w` and `e2`. |
| 84 | + /// - **Swap:** otherwise, some edge `(u, x)` of `B` is currently dominated |
| 85 | + /// only by `e1`. This `x` must lie outside `V(D \ {e1})` and is |
| 86 | + /// therefore distinct from `w`, so `(u, x)` is not adjacent to `e2`. |
| 87 | + /// Replace `e1` with `(u, x)`: `D := (D \ {e1}) \cup {(u, x)}`. Size is |
| 88 | + /// preserved and the adjacent pair at `v` is resolved. |
| 89 | + /// |
| 90 | + /// Each iteration strictly decreases either `|D|` or the number of |
| 91 | + /// adjacent pairs, so the loop terminates in `O(|F|^2)` iterations. Each |
| 92 | + /// iteration scans `O(|F|)` edges to find an adjacent pair, an EDS check, |
| 93 | + /// and a swap candidate, for a total of `O(|F|^3)` time. The result is a |
| 94 | + /// matching that is an EDS, i.e. an independent EDS, which is precisely a |
| 95 | + /// maximal matching. |
73 | 96 | fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> { |
74 | | - let num_source_edges = self.source.graph().num_edges(); |
| 97 | + let graph = self.source.graph(); |
| 98 | + let edges = graph.edges(); |
| 99 | + let num_source_edges = edges.len(); |
| 100 | + let m = graph.left_size(); |
75 | 101 | let target_ones = self.target.ones(); |
76 | | - let bound: usize = target_solution |
| 102 | + |
| 103 | + // Step 1: map selected target 1-entries back to source edge indices. |
| 104 | + // The reduction places source edge `(l_i, r_j)` (in bipartite-local |
| 105 | + // form) at matrix cell `(i, m + j)`, which equals the global edge |
| 106 | + // `(i, m + j)` returned by `Graph::edges()`. Build the lookup from |
| 107 | + // matrix cell -> source edge index so we are robust to any ordering |
| 108 | + // discrepancy between `Graph::edges()` and row-major 1-entries. |
| 109 | + let cell_to_source_edge: std::collections::HashMap<(usize, usize), usize> = edges |
| 110 | + .iter() |
| 111 | + .enumerate() |
| 112 | + .map(|(idx, &(u, v))| { |
| 113 | + // Source edge endpoints in bipartite global coords are |
| 114 | + // (left_idx, m + right_idx); matrix cell is (row=left, col=m+right). |
| 115 | + let (row, col) = if u < m { (u, v) } else { (v, u) }; |
| 116 | + ((row, col), idx) |
| 117 | + }) |
| 118 | + .collect(); |
| 119 | + let mut d: Vec<usize> = target_solution |
77 | 120 | .iter() |
78 | 121 | .zip(target_ones.iter()) |
79 | | - .filter(|(&sel, _)| sel == 1) |
80 | | - .count(); |
81 | | - |
82 | | - // Search for any maximal matching of B with cardinality at most `bound`. |
83 | | - // For an optimal target witness, |D| = mm(B), so such a matching |
84 | | - // exists by Yannakakis-Gavril (1980). For canonical example sizes this |
85 | | - // enumeration is fast; in the worst case it is 2^|E| which mirrors the |
86 | | - // brute-force solve used elsewhere in the test infrastructure. |
87 | | - // |
88 | | - // Iterate by size from 0 upward so we always return a smallest-known |
89 | | - // maximal matching. |
90 | | - for target_size in 0..=bound { |
91 | | - for mask in 0u64..(1u64 << num_source_edges) { |
92 | | - if mask.count_ones() as usize != target_size { |
93 | | - continue; |
94 | | - } |
95 | | - let config: Vec<usize> = (0..num_source_edges) |
96 | | - .map(|i| ((mask >> i) & 1) as usize) |
97 | | - .collect(); |
98 | | - if self.source.is_valid_maximal_matching(&config) { |
99 | | - return config; |
| 122 | + .filter_map(|(&sel, &cell)| { |
| 123 | + if sel == 1 { |
| 124 | + cell_to_source_edge.get(&cell).copied() |
| 125 | + } else { |
| 126 | + None |
100 | 127 | } |
| 128 | + }) |
| 129 | + .collect(); |
| 130 | + |
| 131 | + // Step 2: Yannakakis-Gavril EDS -> independent EDS (maximal matching). |
| 132 | + // Loop invariants: `d` is an EDS of the source graph; each iteration |
| 133 | + // strictly decreases either |d| or the number of (unordered) pairs of |
| 134 | + // adjacent edges inside `d`. |
| 135 | + loop { |
| 136 | + // Find an adjacent pair (e1_idx, e2_idx) inside `d`, sharing vertex v. |
| 137 | + let pair = find_adjacent_pair(&d, &edges); |
| 138 | + let Some((e1_idx, e2_idx, _shared)) = pair else { |
| 139 | + break; // `d` is a matching; we are done. |
| 140 | + }; |
| 141 | + |
| 142 | + // Try dropping e1_idx or e2_idx if the remainder is still an EDS. |
| 143 | + let mut without_e1 = d.clone(); |
| 144 | + without_e1.swap_remove(d.iter().position(|&x| x == e1_idx).unwrap()); |
| 145 | + if is_edge_dominating_set(&without_e1, &edges) { |
| 146 | + d = without_e1; |
| 147 | + continue; |
| 148 | + } |
| 149 | + let mut without_e2 = d.clone(); |
| 150 | + without_e2.swap_remove(d.iter().position(|&x| x == e2_idx).unwrap()); |
| 151 | + if is_edge_dominating_set(&without_e2, &edges) { |
| 152 | + d = without_e2; |
| 153 | + continue; |
| 154 | + } |
| 155 | + |
| 156 | + // Neither drop works -> perform a swap on one of e1 or e2. |
| 157 | + // Choose endpoint not shared with the other edge: for e1=(u, v), |
| 158 | + // e2=(v, w), the "non-shared" endpoint of e1 is u. |
| 159 | + let (e1_a, e1_b) = edges[e1_idx]; |
| 160 | + let (e2_a, e2_b) = edges[e2_idx]; |
| 161 | + let shared = if e1_a == e2_a || e1_a == e2_b { |
| 162 | + e1_a |
| 163 | + } else { |
| 164 | + e1_b |
| 165 | + }; |
| 166 | + let u = if e1_a == shared { e1_b } else { e1_a }; |
| 167 | + let w = if e2_a == shared { e2_b } else { e2_a }; |
| 168 | + |
| 169 | + // Try to swap e1 := (u, x) where x ∉ V(d \ {e1}). The YG proof |
| 170 | + // guarantees such x exists when neither drop succeeded. |
| 171 | + if let Some(new_idx) = find_swap_edge(u, e1_idx, &d, &edges) { |
| 172 | + replace_in(&mut d, e1_idx, new_idx); |
| 173 | + continue; |
| 174 | + } |
| 175 | + // Symmetric swap on e2. |
| 176 | + if let Some(new_idx) = find_swap_edge(w, e2_idx, &d, &edges) { |
| 177 | + replace_in(&mut d, e2_idx, new_idx); |
| 178 | + continue; |
| 179 | + } |
| 180 | + |
| 181 | + // YG guarantees that for an EDS at least one of the four moves |
| 182 | + // above succeeds. Reaching this point implies the input was not |
| 183 | + // a valid EDS (i.e., not a feasible MMD witness on the constructed |
| 184 | + // instance), which violates the reduction's precondition. |
| 185 | + unreachable!( |
| 186 | + "Yannakakis-Gavril EDS->IEDS transformation could not progress; \ |
| 187 | + target witness must be a feasible (dominating) MMD configuration" |
| 188 | + ); |
| 189 | + } |
| 190 | + |
| 191 | + // Step 3: encode the matching as a binary configuration over source edges. |
| 192 | + let mut config = vec![0usize; num_source_edges]; |
| 193 | + for &idx in &d { |
| 194 | + config[idx] = 1; |
| 195 | + } |
| 196 | + config |
| 197 | + } |
| 198 | +} |
| 199 | + |
| 200 | +/// Return `Some((i, j, v))` where `i`, `j` are indices in `d` of two edges that |
| 201 | +/// share vertex `v`, or `None` if all edges in `d` are pairwise independent. |
| 202 | +fn find_adjacent_pair( |
| 203 | + d: &[usize], |
| 204 | + edges: &[(usize, usize)], |
| 205 | +) -> Option<(usize, usize, usize)> { |
| 206 | + for (a_pos, &i) in d.iter().enumerate() { |
| 207 | + let (iu, iv) = edges[i]; |
| 208 | + for &j in &d[a_pos + 1..] { |
| 209 | + let (ju, jv) = edges[j]; |
| 210 | + if iu == ju || iu == jv { |
| 211 | + return Some((i, j, iu)); |
| 212 | + } |
| 213 | + if iv == ju || iv == jv { |
| 214 | + return Some((i, j, iv)); |
101 | 215 | } |
102 | 216 | } |
| 217 | + } |
| 218 | + None |
| 219 | +} |
103 | 220 |
|
104 | | - // Fallback: a zero configuration. This branch is unreachable when the |
105 | | - // reduction is correct and the supplied target witness is feasible. |
106 | | - vec![0; num_source_edges] |
| 221 | +/// Check whether the edge set `d` (indices into `edges`) dominates every edge |
| 222 | +/// of `edges`. An edge `f` is dominated iff `f ∈ d` or `f` shares an endpoint |
| 223 | +/// with some edge in `d`. |
| 224 | +fn is_edge_dominating_set(d: &[usize], edges: &[(usize, usize)]) -> bool { |
| 225 | + // Vertex cover of the candidate EDS. |
| 226 | + let mut covered_vertices: std::collections::HashSet<usize> = |
| 227 | + std::collections::HashSet::new(); |
| 228 | + for &i in d { |
| 229 | + let (u, v) = edges[i]; |
| 230 | + covered_vertices.insert(u); |
| 231 | + covered_vertices.insert(v); |
107 | 232 | } |
| 233 | + edges.iter().enumerate().all(|(f_idx, (u, v))| { |
| 234 | + d.contains(&f_idx) || covered_vertices.contains(u) || covered_vertices.contains(v) |
| 235 | + }) |
| 236 | +} |
| 237 | + |
| 238 | +/// Find an edge index in `edges` that is (i) incident to vertex `endpoint`, |
| 239 | +/// (ii) different from `excluded_idx`, and (iii) whose other endpoint lies |
| 240 | +/// outside `V(d \ {excluded_idx})`. |
| 241 | +/// |
| 242 | +/// This is the swap candidate `(u, x)` from the Yannakakis-Gavril argument |
| 243 | +/// when the drop move is not available for `excluded_idx`. |
| 244 | +fn find_swap_edge( |
| 245 | + endpoint: usize, |
| 246 | + excluded_idx: usize, |
| 247 | + d: &[usize], |
| 248 | + edges: &[(usize, usize)], |
| 249 | +) -> Option<usize> { |
| 250 | + // Vertex cover of d \ {excluded_idx}. |
| 251 | + let mut other_cover: std::collections::HashSet<usize> = std::collections::HashSet::new(); |
| 252 | + for &i in d { |
| 253 | + if i == excluded_idx { |
| 254 | + continue; |
| 255 | + } |
| 256 | + let (u, v) = edges[i]; |
| 257 | + other_cover.insert(u); |
| 258 | + other_cover.insert(v); |
| 259 | + } |
| 260 | + for (k, &(u, v)) in edges.iter().enumerate() { |
| 261 | + if k == excluded_idx { |
| 262 | + continue; |
| 263 | + } |
| 264 | + let (e_endpoint, other) = if u == endpoint { |
| 265 | + (u, v) |
| 266 | + } else if v == endpoint { |
| 267 | + (v, u) |
| 268 | + } else { |
| 269 | + continue; |
| 270 | + }; |
| 271 | + debug_assert_eq!(e_endpoint, endpoint); |
| 272 | + if !other_cover.contains(&other) { |
| 273 | + return Some(k); |
| 274 | + } |
| 275 | + } |
| 276 | + None |
| 277 | +} |
| 278 | + |
| 279 | +/// Replace `old_idx` with `new_idx` inside `d` in-place. Panics if `old_idx` |
| 280 | +/// is not present. |
| 281 | +fn replace_in(d: &mut [usize], old_idx: usize, new_idx: usize) { |
| 282 | + let pos = d |
| 283 | + .iter() |
| 284 | + .position(|&x| x == old_idx) |
| 285 | + .expect("old_idx must be present in d"); |
| 286 | + d[pos] = new_idx; |
108 | 287 | } |
109 | 288 |
|
110 | 289 | #[reduction( |
|
0 commit comments