problemreductions/rules/maxcut_minimummatrixcover.rs
1//! Reduction from MaxCut to MinimumMatrixCover.
2//!
3//! Given a MaxCut instance `(G, w)` with nonnegative integer edge weights,
4//! the target matrix `A` is the symmetric weighted adjacency matrix of `G`:
5//! `a_{ij} = w({i, j})` if `{i, j} ∈ E`, otherwise `0` (diagonal is `0`).
6//!
7//! The key identity is
8//! `Σ_{i,j} a_{ij} · f(i) · f(j) = 2 W − 4 · cut(S)`,
9//! where `S = { i : f(i) = +1 }` and `W = Σ_{e ∈ E} w(e)`. Minimizing the
10//! quadratic form is therefore equivalent to maximizing the cut, and the
11//! reduction is witness-preserving via the identity map between MaxCut's
12//! partition encoding (`config[i] = 1 ⇔ i ∈ S`) and MinimumMatrixCover's
13//! sign encoding (`config[i] = 1 ⇔ f(i) = +1 ⇔ i ∈ S`).
14//!
15//! **Precondition:** all edge weights must be nonnegative. The reduction
16//! panics on any negative weight, since `MinimumMatrixCover` requires a
17//! nonnegative integer matrix. Negative-weight `MaxCut` instances are out
18//! of scope and must use a different (preprocessing) reduction.
19//!
20//! Reference: Garey & Johnson, *Computers and Intractability* (1979),
21//! Appendix A1.2, MS13 ("Transformation from MAXIMUM CUT").
22
23use crate::models::algebraic::MinimumMatrixCover;
24use crate::models::graph::MaxCut;
25use crate::reduction;
26use crate::rules::traits::{ReduceTo, ReductionResult};
27use crate::topology::{Graph, SimpleGraph};
28
29/// Result of reducing MaxCut to MinimumMatrixCover.
30#[derive(Debug, Clone)]
31pub struct ReductionMaxCutToMMC {
32 target: MinimumMatrixCover,
33}
34
35impl ReductionResult for ReductionMaxCutToMMC {
36 type Source = MaxCut<SimpleGraph, i32>;
37 type Target = MinimumMatrixCover;
38
39 fn target_problem(&self) -> &Self::Target {
40 &self.target
41 }
42
43 /// Solution extraction is the identity.
44 ///
45 /// Both encodings agree that bit `i = 1` means vertex `i` is in `S`:
46 /// MaxCut treats `config[i] = 1` as one side of the partition, and
47 /// MinimumMatrixCover treats `config[i] = 1` as `f(i) = +1`, i.e.,
48 /// vertex `i` in `S`. The complementary assignment is equally optimal
49 /// because the quadratic form (and the cut) is invariant under
50 /// `f -> -f`.
51 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
52 target_solution.to_vec()
53 }
54}
55
56#[reduction(
57 overhead = {
58 num_rows = "num_vertices",
59 }
60)]
61impl ReduceTo<MinimumMatrixCover> for MaxCut<SimpleGraph, i32> {
62 type Result = ReductionMaxCutToMMC;
63
64 fn reduce_to(&self) -> Self::Result {
65 let n = self.graph().num_vertices();
66 let mut matrix: Vec<Vec<i64>> = vec![vec![0i64; n]; n];
67
68 for (u, v, w) in self.edges() {
69 assert!(
70 w >= 0,
71 "MaxCut -> MinimumMatrixCover requires nonnegative edge weights, got w({u},{v}) = {w}"
72 );
73 let w64 = w as i64;
74 matrix[u][v] = w64;
75 matrix[v][u] = w64;
76 }
77
78 ReductionMaxCutToMMC {
79 target: MinimumMatrixCover::new(matrix),
80 }
81 }
82}
83
84#[cfg(feature = "example-db")]
85pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
86 use crate::export::SolutionPair;
87
88 vec![crate::example_db::specs::RuleExampleSpec {
89 id: "maxcut_to_minimummatrixcover",
90 build: || {
91 // Canonical example: C_4 (4-cycle) with unit weights.
92 // W = 4, max cut = 4 (partition {0,2} vs {1,3} cuts all edges).
93 // The target's minimum quadratic form value is 2W - 4 * cut = 8 - 16 = -8.
94 let source = MaxCut::<SimpleGraph, i32>::new(
95 SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (0, 3)]),
96 vec![1, 1, 1, 1],
97 );
98 crate::example_db::specs::rule_example_with_witness::<_, MinimumMatrixCover>(
99 source,
100 SolutionPair {
101 // S = {0, 2}: vertices 0 and 2 have f = +1.
102 source_config: vec![1, 0, 1, 0],
103 target_config: vec![1, 0, 1, 0],
104 },
105 )
106 },
107 }]
108}
109
110#[cfg(test)]
111#[path = "../unit_tests/rules/maxcut_minimummatrixcover.rs"]
112mod tests;