Skip to main content

problemreductions/rules/
ksatisfiability_subsetsum.rs

1//! Reduction from KSatisfiability (3-SAT) to SubsetSum.
2//!
3//! Classical Karp reduction using base-10 digit encoding. Each integer has
4//! (n + m) digits, where n is the number of variables and m is the number
5//! of clauses. Variable digits ensure exactly one of y_i/z_i per variable;
6//! clause digits count satisfied literals, padded to 4 by slack integers.
7//!
8//! No carries occur because the maximum digit sum is at most 3 + 2 = 5 < 10.
9//!
10//! Uses `SubsetSum` with arbitrary-precision integers so the encoding does not
11//! overflow on large instances.
12//!
13//! Reference: Karp 1972; Sipser Theorem 7.56; CLRS §34.5.5
14
15use crate::models::formula::KSatisfiability;
16use crate::models::misc::SubsetSum;
17use crate::reduction;
18use crate::rules::traits::{ReduceTo, ReductionResult};
19use crate::variant::K3;
20use num_bigint::BigUint;
21use num_traits::Zero;
22
23/// Result of reducing KSatisfiability<K3> to SubsetSum.
24#[derive(Debug, Clone)]
25pub struct Reduction3SATToSubsetSum {
26    target: SubsetSum,
27    source_num_vars: usize,
28}
29
30impl ReductionResult for Reduction3SATToSubsetSum {
31    type Source = KSatisfiability<K3>;
32    type Target = SubsetSum;
33
34    fn target_problem(&self) -> &Self::Target {
35        &self.target
36    }
37
38    fn extract_solution(
39        &self,
40        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
41    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
42        crate::rules::traits::validate_target_witness(
43            self.target_problem(),
44            target_solution,
45            |value| value.0,
46            "target witness does not satisfy the target problem",
47        )?;
48
49        Ok({
50            // Variable integers are the first 2n elements in 0-based indexing:
51            // for variable i (0 <= i < n), y_i is stored at index 2*i and z_i at index 2*i + 1.
52            // If y_i is selected (target_solution[2*i]), set x_i = 1; otherwise x_i = 0.
53            (0..self.source_num_vars)
54                .map(|i| target_solution[2 * i])
55                .collect()
56        })
57    }
58}
59
60/// Build a base-10 integer from a digit array (most significant first).
61///
62/// digits[0] is the most significant digit, digits[num_digits-1] is the least.
63fn digits_to_integer(digits: &[u8]) -> BigUint {
64    let mut value = BigUint::zero();
65    let ten = BigUint::from(10u8);
66    for &d in digits {
67        value = value * &ten + BigUint::from(d);
68    }
69    value
70}
71
72#[crate::aggregate_reduction(identity)]
73impl crate::rules::AggregateReductionResult for Reduction3SATToSubsetSum {}
74
75// Every integer has at most n+m decimal digits, and 10 < 2^4. The extra 1
76// also covers the empty formula's zero target (whose magnitude parameter is 1).
77#[reduction(transform = upper_bound {
78    num_elements = "2 * num_vars + 2 * num_clauses",
79    max_numeric_magnitude_bits = "4 * (num_vars + num_clauses) + 1",
80})]
81impl ReduceTo<SubsetSum> for KSatisfiability<K3> {
82    type Result = Reduction3SATToSubsetSum;
83
84    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
85        let n = self.num_vars();
86        let m = self.num_clauses();
87        let num_digits = n + m;
88
89        let mut sizes = Vec::with_capacity(2 * n + 2 * m);
90
91        // Step 1: Variable integers (2n integers)
92        for i in 0..n {
93            // y_i: d_i = 1, d_{n+j} = 1 if x_{i+1} appears positive in C_j
94            let mut y_digits = vec![0u8; num_digits];
95            y_digits[i] = 1;
96            for (j, clause) in self.clauses().iter().enumerate() {
97                for &lit in &clause.literals {
98                    let var_idx = (lit.unsigned_abs() as usize) - 1;
99                    if var_idx == i && lit > 0 {
100                        y_digits[n + j] = 1;
101                    }
102                }
103            }
104            sizes.push(digits_to_integer(&y_digits));
105
106            // z_i: d_i = 1, d_{n+j} = 1 if ¬x_{i+1} appears in C_j
107            let mut z_digits = vec![0u8; num_digits];
108            z_digits[i] = 1;
109            for (j, clause) in self.clauses().iter().enumerate() {
110                for &lit in &clause.literals {
111                    let var_idx = (lit.unsigned_abs() as usize) - 1;
112                    if var_idx == i && lit < 0 {
113                        z_digits[n + j] = 1;
114                    }
115                }
116            }
117            sizes.push(digits_to_integer(&z_digits));
118        }
119
120        // Step 2: Slack integers (2m integers)
121        for j in 0..m {
122            // g_j: d_{n+j} = 1
123            let mut g_digits = vec![0u8; num_digits];
124            g_digits[n + j] = 1;
125            sizes.push(digits_to_integer(&g_digits));
126
127            // h_j: d_{n+j} = 2
128            let mut h_digits = vec![0u8; num_digits];
129            h_digits[n + j] = 2;
130            sizes.push(digits_to_integer(&h_digits));
131        }
132
133        // Step 3: Target
134        let mut target_digits = vec![0u8; num_digits];
135        for d in target_digits.iter_mut().take(n) {
136            *d = 1;
137        }
138        for d in target_digits.iter_mut().skip(n).take(m) {
139            *d = 4;
140        }
141        let target = digits_to_integer(&target_digits);
142
143        Ok(Reduction3SATToSubsetSum {
144            target: SubsetSum::new_unchecked(sizes, target),
145            source_num_vars: n,
146        })
147    }
148}
149
150#[cfg(feature = "example-db")]
151pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
152    use crate::export::SolutionPair;
153    use crate::models::formula::CNFClause;
154    use crate::models::misc::SubsetSum;
155
156    vec![crate::example_db::specs::RuleExampleSpec {
157        id: "ksatisfiability_to_subsetsum",
158        build: || {
159            let source = KSatisfiability::<K3>::new(
160                3,
161                vec![
162                    CNFClause::new(vec![1, 2, 3]),
163                    CNFClause::new(vec![-1, -2, 3]),
164                ],
165            );
166            crate::example_db::specs::rule_example_with_witness::<_, SubsetSum>(
167                source,
168                SolutionPair {
169                    source_config: serde_json::json!(vec![false, false, true]),
170                    target_config: serde_json::json!(vec![
171                        false, true, false, true, true, false, true, true, true, false
172                    ]),
173                },
174            )
175        },
176    }]
177}
178
179#[cfg(test)]
180#[path = "../unit_tests/rules/ksatisfiability_subsetsum.rs"]
181mod tests;