Skip to main content

problemreductions/rules/
threedimensionalmatching_minimumweightdecoding.rs

1//! Reduction from ThreeDimensionalMatching to MinimumWeightDecoding.
2//!
3//! This is the classical Berlekamp–McEliece–van Tilborg (1978) construction
4//! (Garey & Johnson MS7) that establishes NP-hardness of the minimum-weight
5//! codeword problem. Each triple `t_j = (a_j, b_j, c_j)` becomes a column of
6//! a `3q × m` parity-check matrix `H` with exactly three 1s (one per row
7//! block `W`, `X`, `Y`), and the syndrome is the all-ones vector `1^{3q}`.
8//!
9//! **Bridge.** This is a *witness* reduction from `ThreeDimensionalMatching`
10//! (`Value = Or`) to `MinimumWeightDecoding` (`Value = Min<usize>`):
11//!
12//! `source.evaluate(S) == Or(true)` ⇔ `target.evaluate(x) == Min(Some(q))`,
13//!
14//! where `S = { t_j ∈ T : x_j = 1 }`. We rely on the witness-extraction
15//! route `source.evaluate(extract_solution(x))` rather than comparing the
16//! optimum value directly, mirroring `partition_sumofsquarespartition.rs`.
17//!
18//! **Sentinel branch.** `MinimumWeightDecoding::new` panics on zero-row or
19//! zero-column matrices, so degenerate inputs (`q = 0` or `T = []`) emit a
20//! fixed `1×1` sentinel `H = [[1]]` with syndrome `s = [0]`. The unique
21//! feasible codeword `x = (0)` decodes to the empty subset `S = ∅`, and
22//! `source.evaluate(∅)` correctly returns `Or(true)` iff `q = 0`.
23
24use crate::models::algebraic::MinimumWeightDecoding;
25use crate::models::set::ThreeDimensionalMatching;
26use crate::reduction;
27use crate::rules::traits::{ReduceTo, ReductionResult};
28
29/// Result of reducing ThreeDimensionalMatching to MinimumWeightDecoding.
30#[derive(Debug, Clone)]
31pub struct ReductionThreeDimensionalMatchingToMinimumWeightDecoding {
32    target: MinimumWeightDecoding,
33    /// Number of triples in the original 3DM instance.
34    /// Used to return a correctly-sized witness when the sentinel path is
35    /// taken (i.e. `q == 0` or `num_triples == 0`).
36    source_num_triples: usize,
37}
38
39impl ReductionResult for ReductionThreeDimensionalMatchingToMinimumWeightDecoding {
40    type Source = ThreeDimensionalMatching;
41    type Target = MinimumWeightDecoding;
42
43    fn target_problem(&self) -> &Self::Target {
44        &self.target
45    }
46
47    /// Solution extraction: identity mapping in the main branch. The target
48    /// codeword `x ∈ {0,1}^m` is the source subset indicator over the same
49    /// triple index set. In the sentinel branch the target witness has length
50    /// `1` (always `[0]`); we return the all-zero source-sized vector,
51    /// which decodes to `S = ∅`. `ThreeDimensionalMatching::evaluate(∅)`
52    /// then yields `Or(true)` iff `q == 0` (the correct answer for both
53    /// sentinel sub-cases).
54    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
55        if target_solution.len() == self.source_num_triples {
56            target_solution.to_vec()
57        } else {
58            vec![0; self.source_num_triples]
59        }
60    }
61}
62
63#[reduction(overhead = {
64    num_rows = "3 * universe_size",
65    num_cols = "num_triples",
66})]
67impl ReduceTo<MinimumWeightDecoding> for ThreeDimensionalMatching {
68    type Result = ReductionThreeDimensionalMatchingToMinimumWeightDecoding;
69
70    fn reduce_to(&self) -> Self::Result {
71        let q = self.universe_size();
72        let m = self.num_triples();
73
74        if q == 0 || m == 0 {
75            // Sentinel: MinimumWeightDecoding::new panics on empty matrices
76            // or zero-column matrices. Build a fixed 1×1 instance whose only
77            // feasible codeword is x = (0), decoding to S = ∅. The source's
78            // own evaluate on the empty set gives the correct answer:
79            //   q = 0 → Or(true)  (empty matching of empty universe)
80            //   q ≥ 1 → Or(false) (no triples cannot cover non-empty universe).
81            return ReductionThreeDimensionalMatchingToMinimumWeightDecoding {
82                target: MinimumWeightDecoding::new(vec![vec![true]], vec![false]),
83                source_num_triples: m,
84            };
85        }
86
87        // Main branch: build H ∈ {0,1}^{3q × m} with row blocks W, X, Y and
88        // one 1 per row block per column at the triple's coordinate.
89        let num_rows = 3 * q;
90        let mut matrix = vec![vec![false; m]; num_rows];
91        for (j, &(a, b, c)) in self.triples().iter().enumerate() {
92            matrix[a][j] = true;
93            matrix[q + b][j] = true;
94            matrix[2 * q + c][j] = true;
95        }
96        let syndrome = vec![true; num_rows];
97
98        ReductionThreeDimensionalMatchingToMinimumWeightDecoding {
99            target: MinimumWeightDecoding::new(matrix, syndrome),
100            source_num_triples: m,
101        }
102    }
103}
104
105#[cfg(feature = "example-db")]
106pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
107    use crate::export::SolutionPair;
108
109    vec![crate::example_db::specs::RuleExampleSpec {
110        id: "threedimensionalmatching_to_minimumweightdecoding",
111        build: || {
112            // q = 2, T = [(0,0,0), (1,1,1), (0,1,0), (1,0,1)].
113            // Perfect matchings: {t_0, t_1} and {t_2, t_3} -- both attain
114            // target minimum weight = q = 2.
115            crate::example_db::specs::rule_example_with_witness::<_, MinimumWeightDecoding>(
116                ThreeDimensionalMatching::new(2, vec![(0, 0, 0), (1, 1, 1), (0, 1, 0), (1, 0, 1)]),
117                SolutionPair {
118                    source_config: vec![1, 1, 0, 0],
119                    target_config: vec![1, 1, 0, 0],
120                },
121            )
122        },
123    }]
124}
125
126#[cfg(test)]
127#[path = "../unit_tests/rules/threedimensionalmatching_minimumweightdecoding.rs"]
128mod tests;