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    /// The target codeword prefix is the source subset indicator over the same
48    /// triple index set. The sentinel target appends one synthetic column, so
49    /// an empty source maps back to the empty prefix.
50    fn extract_solution(
51        &self,
52        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
53    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
54        crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
55        if target_solution.len() != self.target.num_cols() {
56            return Err(crate::rules::ExtractionError::invalid(format!(
57                "expected {} target codeword bits, got {}",
58                self.target.num_cols(),
59                target_solution.len()
60            )));
61        }
62
63        Ok(target_solution[..self.source_num_triples].to_vec())
64    }
65}
66
67#[reduction(
68    transform = exact {
69        num_rows = "3 * universe_size",
70        num_cols = "num_triples",
71    })]
72impl ReduceTo<MinimumWeightDecoding> for ThreeDimensionalMatching {
73    type Result = ReductionThreeDimensionalMatchingToMinimumWeightDecoding;
74
75    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
76        let q = self.universe_size();
77        let m = self.num_triples();
78
79        if q == 0 || m == 0 {
80            // Sentinel: MinimumWeightDecoding::new panics on empty matrices
81            // or zero-column matrices. Build a fixed 1×1 instance whose only
82            // feasible codeword is x = (0), decoding to S = ∅. The source's
83            // own evaluate on the empty set gives the correct answer:
84            //   q = 0 → Or(true)  (empty matching of empty universe)
85            //   q ≥ 1 → Or(false) (no triples cannot cover non-empty universe).
86            return Ok(ReductionThreeDimensionalMatchingToMinimumWeightDecoding {
87                target: MinimumWeightDecoding::new(vec![vec![true]], vec![false]),
88                source_num_triples: m,
89            });
90        }
91
92        // Main branch: build H ∈ {0,1}^{3q × m} with row blocks W, X, Y and
93        // one 1 per row block per column at the triple's coordinate.
94        let num_rows = 3 * q;
95        let mut matrix = vec![vec![false; m]; num_rows];
96        for (j, &(a, b, c)) in self.triples().iter().enumerate() {
97            matrix[a][j] = true;
98            matrix[q + b][j] = true;
99            matrix[2 * q + c][j] = true;
100        }
101        let syndrome = vec![true; num_rows];
102
103        Ok(ReductionThreeDimensionalMatchingToMinimumWeightDecoding {
104            target: MinimumWeightDecoding::new(matrix, syndrome),
105            source_num_triples: m,
106        })
107    }
108}
109
110#[cfg(feature = "example-db")]
111pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
112    use crate::export::SolutionPair;
113
114    vec![crate::example_db::specs::RuleExampleSpec {
115        id: "threedimensionalmatching_to_minimumweightdecoding",
116        build: || {
117            // q = 2, T = [(0,0,0), (1,1,1), (0,1,0), (1,0,1)].
118            // Perfect matchings: {t_0, t_1} and {t_2, t_3} -- both attain
119            // target minimum weight = q = 2.
120            crate::example_db::specs::rule_example_with_witness::<_, MinimumWeightDecoding>(
121                ThreeDimensionalMatching::new(2, vec![(0, 0, 0), (1, 1, 1), (0, 1, 0), (1, 0, 1)]),
122                SolutionPair {
123                    source_config: serde_json::json!(vec![true, true, false, false]),
124                    target_config: serde_json::json!(vec![true, true, false, false]),
125                },
126            )
127        },
128    }]
129}
130
131#[cfg(test)]
132#[path = "../unit_tests/rules/threedimensionalmatching_minimumweightdecoding.rs"]
133mod tests;