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 }`. The completed optimum maps to YES/NO
15//! by comparison with `q`; only a weight-`q` target witness is decoded.
16//!
17//! **Sentinel branch.** `MinimumWeightDecoding::new` panics on zero-row or
18//! zero-column matrices, so degenerate inputs (`q = 0` or `T = []`) emit a
19//! fixed `1×1` sentinel `H = [[1]]` with syndrome `s = [0]`. The unique
20//! feasible codeword `x = (0)` has weight zero, so the aggregate mapping returns
21//! YES iff `q = 0`. Only that YES case decodes to the empty subset.
22
23use crate::models::algebraic::MinimumWeightDecoding;
24use crate::models::set::ThreeDimensionalMatching;
25use crate::reduction;
26use crate::rules::traits::{ReduceTo, ReductionResult};
27
28/// Result of reducing ThreeDimensionalMatching to MinimumWeightDecoding.
29#[derive(Debug, Clone)]
30pub struct ReductionThreeDimensionalMatchingToMinimumWeightDecoding {
31    target: MinimumWeightDecoding,
32    /// Number of triples in the original 3DM instance.
33    /// Used to return a correctly-sized witness when the sentinel path is
34    /// taken (i.e. `q == 0` or `num_triples == 0`).
35    source_num_triples: usize,
36    source_universe_size: 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_witness(
55            self.target_problem(),
56            target_solution,
57            |value| crate::rules::AggregateReductionResult::extract_value(self, value).0,
58            "target witness does not certify a YES answer for the source",
59        )?;
60        if target_solution.len() != self.target.num_cols() {
61            return Err(crate::rules::ExtractionError::invalid(format!(
62                "expected {} target codeword bits, got {}",
63                self.target.num_cols(),
64                target_solution.len()
65            )));
66        }
67
68        Ok(target_solution[..self.source_num_triples].to_vec())
69    }
70}
71
72#[crate::aggregate_reduction]
73impl crate::rules::AggregateReductionResult
74    for ReductionThreeDimensionalMatchingToMinimumWeightDecoding
75{
76    type Source = ThreeDimensionalMatching;
77    type Target = MinimumWeightDecoding;
78
79    fn target_problem(&self) -> &Self::Target {
80        &self.target
81    }
82
83    fn extract_value(&self, value: crate::types::Min<i64>) -> crate::types::Or {
84        crate::types::Or(
85            value
86                .0
87                .is_some_and(|count| i128::from(count) == self.source_universe_size as i128),
88        )
89    }
90}
91
92#[reduction(
93    transform = exact {
94        num_rows = "3 * universe_size",
95        num_cols = "num_triples",
96    })]
97impl ReduceTo<MinimumWeightDecoding> for ThreeDimensionalMatching {
98    type Result = ReductionThreeDimensionalMatchingToMinimumWeightDecoding;
99
100    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
101        let q = self.universe_size();
102        let m = self.num_triples();
103
104        if q == 0 || m == 0 {
105            // Sentinel: MinimumWeightDecoding::new panics on empty matrices
106            // or zero-column matrices. Build a fixed 1×1 instance whose only
107            // feasible codeword is x = (0), decoding to S = ∅. The source's
108            // own evaluate on the empty set gives the correct answer:
109            //   q = 0 → Or(true)  (empty matching of empty universe)
110            //   q ≥ 1 → Or(false) (no triples cannot cover non-empty universe).
111            return Ok(ReductionThreeDimensionalMatchingToMinimumWeightDecoding {
112                source_universe_size: q,
113                target: MinimumWeightDecoding::new(vec![vec![true]], vec![false]),
114                source_num_triples: m,
115            });
116        }
117
118        // Main branch: build H ∈ {0,1}^{3q × m} with row blocks W, X, Y and
119        // one 1 per row block per column at the triple's coordinate.
120        let num_rows = 3 * q;
121        let mut matrix = vec![vec![false; m]; num_rows];
122        for (j, &(a, b, c)) in self.triples().iter().enumerate() {
123            matrix[a][j] = true;
124            matrix[q + b][j] = true;
125            matrix[2 * q + c][j] = true;
126        }
127        let syndrome = vec![true; num_rows];
128
129        Ok(ReductionThreeDimensionalMatchingToMinimumWeightDecoding {
130            source_universe_size: q,
131            target: MinimumWeightDecoding::new(matrix, syndrome),
132            source_num_triples: m,
133        })
134    }
135}
136
137#[cfg(feature = "example-db")]
138pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
139    use crate::export::SolutionPair;
140
141    vec![crate::example_db::specs::RuleExampleSpec {
142        id: "threedimensionalmatching_to_minimumweightdecoding",
143        build: || {
144            // q = 2, T = [(0,0,0), (1,1,1), (0,1,0), (1,0,1)].
145            // Perfect matchings: {t_0, t_1} and {t_2, t_3} -- both attain
146            // target minimum weight = q = 2.
147            crate::example_db::specs::rule_example_with_witness::<_, MinimumWeightDecoding>(
148                ThreeDimensionalMatching::new(2, vec![(0, 0, 0), (1, 1, 1), (0, 1, 0), (1, 0, 1)]),
149                SolutionPair {
150                    source_config: serde_json::json!(vec![true, true, false, false]),
151                    target_config: serde_json::json!(vec![true, true, false, false]),
152                },
153            )
154        },
155    }]
156}
157
158#[cfg(test)]
159#[path = "../unit_tests/rules/threedimensionalmatching_minimumweightdecoding.rs"]
160mod tests;