Skip to content

Commit 5f348a8

Browse files
committed
Fix nits: MinimumMaximalMatching -> MinimumMatrixDomination (#847)
1 parent 09fb3fd commit 5f348a8

3 files changed

Lines changed: 270 additions & 40 deletions

File tree

docs/paper/reductions.typ

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14517,7 +14517,7 @@ The following reductions to Integer Linear Programming are straightforward formu
1451714517

1451814518
*Step 4 -- Target optimum.* The fixture selects the matrix-domination set $C = {(0, 2), (1, 3)}$ at 1-entry indices #selected-target.map(i => str(i)).join(", "). Every other 1-entry of $M$ shares row $0$ or row $1$ with one of the chosen entries, so $C$ is dominating with $|C| = #selected-target.len() = 2 = "mm"(B) #sym.checkmark$.
1451914519

14520-
*Extraction.* The selected 1-entries here happen to be pairwise independent (no shared row or column), so they already correspond to a matching of $B$. In general a matrix-domination witness only yields an edge dominating set, and the Yannakakis-Gavril transformation @yannakakis1980 converts it to a maximal matching of equal size.
14520+
*Extraction.* For this fixture the selected 1-entries happen to be pairwise independent (no shared row or column), so they already correspond to a matching of $B$. In general a matrix-domination witness only maps back to an edge dominating set $F_C subset.eq F$ that need not be a matching: for instance, the optimal witness $C = {(0, 3), (0, 4)}$ corresponds to source edges ${(l_0, r_1), (l_0, r_2)}$, which share endpoint $l_0$. In such cases the polynomial-time Yannakakis-Gavril transformation @yannakakis1980 -- implemented in `extract_solution` as a sequence of drop / swap moves on $F_C$ -- converts $F_C$ into a maximal matching of $B$ of the same or smaller size, here e.g. ${(l_0, r_0), (l_1, r_1)}$ or ${(l_0, r_1), (l_1, r_2)}$.
1452114521
]
1452214522
}
1452314523
],
@@ -14543,7 +14543,7 @@ The following reductions to Integer Linear Programming are straightforward formu
1454314543

1454414544
($arrow.l.double$) A dominating set $C$ of $M$ with $|C| <= K'$ corresponds to an EDS $F_C$ of size $|C| <= K' = K$. Applying the polynomial-time Yannakakis-Gavril transformation to $F_C$ yields a maximal matching of $B$ of the same size, so $"mm"(B) <= K$.
1454514545

14546-
_Solution extraction._ Read the selected 1-entries of the matrix-domination witness, map each $(i, m + j)$ back to the bipartite edge $(l_i, r_j)$ to obtain an EDS $F_C$ of $B$, and apply the Yannakakis-Gavril EDS-to-maximal-matching conversion to recover a maximal matching $M^*$ with $|M^*| = |F_C|$.
14546+
_Solution extraction._ Read the selected 1-entries of the matrix-domination witness, map each $(i, m + j)$ back to the bipartite edge $(l_i, r_j)$ to obtain an EDS $F_C$ of $B$. Arbitrary optimal MMD witnesses may select 1-entries whose corresponding source edges form a connected subgraph (e.g. two edges sharing a left endpoint) rather than a matching; in that case the polynomial-time Yannakakis-Gavril EDS-to-IEDS transformation iteratively resolves each adjacent pair in $F_C$ by either dropping a redundant edge or swapping it for an edge whose new endpoint lies outside the current vertex cover, terminating in $O(|F|^3)$ time. The result is a maximal matching $M^*$ of $B$ with $|M^*| <= |F_C|$, which `extract_solution` returns as the source-side configuration.
1454714547

1454814548
_Note on source variant._ The reduction crucially requires the source graph to be bipartite. The biadjacency matrix faithfully represents the edge structure of $B$ (each edge contributes exactly one 1-entry). The adjacency matrix of a general undirected graph would produce two symmetric 1-entries per edge that do not preserve the row/column sharing pattern.
1454914549
]

src/rules/minimummaximalmatching_minimummatrixdomination.rs

Lines changed: 217 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
//!
1717
//! Solving Minimum Matrix Domination on the constructed instance yields a
1818
//! 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
2020
//! 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.
2627
//!
2728
//! ## Source variant
2829
//!
@@ -60,51 +61,229 @@ impl ReductionResult for ReductionMMMToMatrixDomination {
6061
}
6162

6263
/// 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.
6466
///
6567
/// The target witness identifies a set of 1-entries of `M`. Each selected
6668
/// 1-entry in the upper-right block `B*` corresponds bijectively to a
6769
/// 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.
7396
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();
75101
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
77120
.iter()
78121
.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
100127
}
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));
101215
}
102216
}
217+
}
218+
None
219+
}
103220

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);
107232
}
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;
108287
}
109288

110289
#[reduction(

src/unit_tests/rules/minimummaximalmatching_minimummatrixdomination.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,57 @@ fn test_no_instance_unreachable_threshold() {
133133
}
134134
}
135135

136+
#[test]
137+
fn test_extract_solution_yg_transform_on_non_matching_eds() {
138+
// Verify that the Yannakakis-Gavril EDS->IEDS transformation correctly
139+
// handles a target witness whose corresponding source edges form a
140+
// *connected* subgraph (two edges sharing l0), not yet a matching.
141+
//
142+
// On the YES bipartite graph, the EDS {(l0, r1), (l0, r2)} is a size-2
143+
// minimum EDS that shares vertex l0. Its image in the matrix is the
144+
// 1-entries at indices 1 and 2 (positions (0, 3) and (0, 4)). This
145+
// dominates all other 1-entries via row 0 / column 3 / column 4.
146+
//
147+
// The naive "drop one edge" reduction fails: removing either of
148+
// {(l0, r1), (l0, r2)} from D leaves a vertex (l1 or r2/r1) whose
149+
// incident edge (l1, r2) is no longer dominated. The transformation
150+
// must therefore swap one of the adjacent edges for a different bipartite
151+
// edge whose new endpoint lies outside V(D \ {e}). The result is a valid
152+
// maximal matching of size <= 2, e.g. {(l0, r0), (l1, r1)} or
153+
// {(l0, r1), (l1, r2)}.
154+
let source = MinimumMaximalMatching::new(yes_bipartite());
155+
let reduction = ReduceTo::<MinimumMatrixDomination>::reduce_to(&source);
156+
157+
// Construct the non-matching EDS witness explicitly. ones() ordering on
158+
// this instance is [(0,2),(0,3),(0,4),(1,3),(1,4)]; indices 1 and 2
159+
// pick (0,3) = source edge (l0, r1) and (0,4) = source edge (l0, r2).
160+
let target_witness = vec![0, 1, 1, 0, 0];
161+
162+
// Sanity-check: this is actually a feasible MMD witness on the target.
163+
let target = reduction.target_problem();
164+
assert_eq!(target.evaluate(&target_witness), Min(Some(2)));
165+
166+
let extracted = reduction.extract_solution(&target_witness);
167+
168+
// The extracted configuration must be a valid maximal matching of B of
169+
// size 2 (= mm(B)). Crucially it cannot be {(l0, r1), (l0, r2)} because
170+
// those two source edges share l0 and are not a matching.
171+
assert!(
172+
source.is_valid_maximal_matching(&extracted),
173+
"YG transform must produce a maximal matching, got {extracted:?}"
174+
);
175+
let size: usize = extracted.iter().sum();
176+
assert_eq!(
177+
size, 2,
178+
"extracted matching must have size mm(B) = 2, got size {size}"
179+
);
180+
assert!(
181+
!(extracted[1] == 1 && extracted[2] == 1),
182+
"transform must break the (l0,r1)-(l0,r2) adjacency"
183+
);
184+
assert_eq!(source.evaluate(&extracted), Min(Some(2)));
185+
}
186+
136187
#[test]
137188
fn test_identity_on_random_bipartite_instances() {
138189
// Verify the Yannakakis-Gavril identity mm(B) = min matrix domination of

0 commit comments

Comments
 (0)