Skip to main content

problemreductions/rules/
ksatisfiability_registersufficiency.rs

1//! Reduction from KSatisfiability (3-SAT) to RegisterSufficiency.
2//!
3//! This is Sethi's Reduction I / Theorem 3.11 with the corrected extraction
4//! rule from issue #872:
5//! - the snapshot is taken immediately after `w[n]`
6//! - `x_k = true` iff `x_pos[k]` has been computed by that snapshot
7//! - at most one of `x_pos[k]`, `x_neg[k]` can have been computed by then
8//! - the literal/clause edges keep Sethi's original orientation
9
10use crate::models::formula::KSatisfiability;
11use crate::models::misc::RegisterSufficiency;
12use crate::reduction;
13use crate::rules::traits::{ReduceTo, ReductionResult};
14use crate::variant::K3;
15
16#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
17#[derive(Debug, Clone)]
18struct SethiRegisterLayout {
19    num_vars: usize,
20    num_clauses: usize,
21    b_padding: usize,
22    a_start: usize,
23    b_start: usize,
24    c_start: usize,
25    f_start: usize,
26    initial_idx: usize,
27    d_idx: usize,
28    final_idx: usize,
29    r_starts: Vec<usize>,
30    s_starts: Vec<usize>,
31    t_starts: Vec<usize>,
32    u_start: usize,
33    w_start: usize,
34    x_pos_start: usize,
35    x_neg_start: usize,
36    z_start: usize,
37    total_vertices: usize,
38}
39
40#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
41impl SethiRegisterLayout {
42    fn new(num_vars: usize, num_clauses: usize) -> Self {
43        let b_padding = (2 * num_vars).saturating_sub(num_clauses);
44        let mut next = 0usize;
45
46        let a_start = next;
47        next += 2 * num_vars + 1;
48
49        let b_start = next;
50        next += b_padding;
51
52        let c_start = next;
53        next += num_clauses;
54
55        let f_start = next;
56        next += 3 * num_clauses;
57
58        let initial_idx = next;
59        let d_idx = next + 1;
60        let final_idx = next + 2;
61        next += 3;
62
63        let mut r_starts = Vec::with_capacity(num_vars);
64        for var in 0..num_vars {
65            r_starts.push(next);
66            next += 2 * num_vars - 2 * var;
67        }
68
69        let mut s_starts = Vec::with_capacity(num_vars);
70        for var in 0..num_vars {
71            s_starts.push(next);
72            next += 2 * num_vars - 2 * var - 1;
73        }
74
75        let mut t_starts = Vec::with_capacity(num_vars);
76        for var in 0..num_vars {
77            t_starts.push(next);
78            next += 2 * num_vars - 2 * var - 1;
79        }
80
81        let u_start = next;
82        next += 2 * num_vars;
83
84        let w_start = next;
85        next += num_vars;
86
87        let x_pos_start = next;
88        next += num_vars;
89
90        let x_neg_start = next;
91        next += num_vars;
92
93        let z_start = next;
94        next += num_vars;
95
96        Self {
97            num_vars,
98            num_clauses,
99            b_padding,
100            a_start,
101            b_start,
102            c_start,
103            f_start,
104            initial_idx,
105            d_idx,
106            final_idx,
107            r_starts,
108            s_starts,
109            t_starts,
110            u_start,
111            w_start,
112            x_pos_start,
113            x_neg_start,
114            z_start,
115            total_vertices: next,
116        }
117    }
118
119    fn total_vertices(&self) -> usize {
120        self.total_vertices
121    }
122
123    fn bound(&self) -> usize {
124        3 * self.num_clauses + 4 * self.num_vars + 1 + self.b_padding
125    }
126
127    fn initial(&self) -> usize {
128        self.initial_idx
129    }
130
131    fn d(&self) -> usize {
132        self.d_idx
133    }
134
135    fn final_node(&self) -> usize {
136        self.final_idx
137    }
138
139    fn a(&self, index: usize) -> usize {
140        self.a_start + index
141    }
142
143    fn bnode(&self, index: usize) -> usize {
144        self.b_start + index
145    }
146
147    fn c(&self, clause: usize) -> usize {
148        self.c_start + clause
149    }
150
151    fn f(&self, clause: usize, literal_pos: usize) -> usize {
152        self.f_start + 3 * clause + literal_pos
153    }
154
155    fn r(&self, var: usize, index: usize) -> usize {
156        self.r_starts[var] + index
157    }
158
159    fn s(&self, var: usize, index: usize) -> usize {
160        self.s_starts[var] + index
161    }
162
163    fn t(&self, var: usize, index: usize) -> usize {
164        self.t_starts[var] + index
165    }
166
167    fn u(&self, var: usize, slot: usize) -> usize {
168        self.u_start + 2 * var + slot
169    }
170
171    fn w(&self, var: usize) -> usize {
172        self.w_start + var
173    }
174
175    fn x_pos(&self, var: usize) -> usize {
176        self.x_pos_start + var
177    }
178
179    fn x_neg(&self, var: usize) -> usize {
180        self.x_neg_start + var
181    }
182
183    fn z(&self, var: usize) -> usize {
184        self.z_start + var
185    }
186}
187
188#[derive(Debug, Clone)]
189pub struct Reduction3SATToRegisterSufficiency {
190    target: RegisterSufficiency,
191    layout: SethiRegisterLayout,
192}
193
194impl ReductionResult for Reduction3SATToRegisterSufficiency {
195    type Source = KSatisfiability<K3>;
196    type Target = RegisterSufficiency;
197
198    fn target_problem(&self) -> &Self::Target {
199        &self.target
200    }
201
202    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
203        if self.layout.num_vars == 0 {
204            return Vec::new();
205        }
206
207        let cutoff = target_solution[self.layout.w(self.layout.num_vars - 1)];
208        (0..self.layout.num_vars)
209            .map(|var| {
210                let x_pos_before = target_solution[self.layout.x_pos(var)] < cutoff;
211                let x_neg_before = target_solution[self.layout.x_neg(var)] < cutoff;
212                debug_assert!(
213                    !(x_pos_before && x_neg_before),
214                    "Sethi extraction expects at most one of x_pos/x_neg before w[n]",
215                );
216                usize::from(x_pos_before)
217            })
218            .collect()
219    }
220}
221
222#[reduction(overhead = {
223    num_vertices = "3 * num_vars^2 + 9 * num_vars + 4 * num_clauses + register_sufficiency_padding + 4",
224    num_arcs = "6 * num_vars^2 + 19 * num_vars + 16 * num_clauses + 2 * register_sufficiency_padding + 1",
225    bound = "3 * num_clauses + 4 * num_vars + 1 + register_sufficiency_padding",
226})]
227impl ReduceTo<RegisterSufficiency> for KSatisfiability<K3> {
228    type Result = Reduction3SATToRegisterSufficiency;
229
230    fn reduce_to(&self) -> Self::Result {
231        let layout = SethiRegisterLayout::new(self.num_vars(), self.num_clauses());
232        let mut arcs = Vec::with_capacity(
233            6 * self.num_vars() * self.num_vars()
234                + 19 * self.num_vars()
235                + 16 * self.num_clauses()
236                + 2 * layout.b_padding
237                + 1,
238        );
239
240        for index in 0..(2 * self.num_vars() + 1) {
241            arcs.push((layout.initial(), layout.a(index)));
242        }
243        for index in 0..layout.b_padding {
244            arcs.push((layout.initial(), layout.bnode(index)));
245        }
246        for clause in 0..self.num_clauses() {
247            for literal_pos in 0..3 {
248                arcs.push((layout.initial(), layout.f(clause, literal_pos)));
249            }
250        }
251        for var in 0..self.num_vars() {
252            arcs.push((layout.initial(), layout.u(var, 0)));
253            arcs.push((layout.initial(), layout.u(var, 1)));
254        }
255
256        for clause in 0..self.num_clauses() {
257            arcs.push((layout.c(clause), layout.initial()));
258        }
259        for var in 0..self.num_vars() {
260            for index in 0..(2 * self.num_vars() - 2 * var) {
261                arcs.push((layout.r(var, index), layout.initial()));
262            }
263            for index in 0..(2 * self.num_vars() - 2 * var - 1) {
264                arcs.push((layout.s(var, index), layout.initial()));
265                arcs.push((layout.t(var, index), layout.initial()));
266            }
267            arcs.push((layout.w(var), layout.initial()));
268        }
269
270        for var in 0..self.num_vars() {
271            arcs.push((layout.final_node(), layout.w(var)));
272            arcs.push((layout.final_node(), layout.x_pos(var)));
273            arcs.push((layout.final_node(), layout.x_neg(var)));
274            arcs.push((layout.final_node(), layout.z(var)));
275        }
276        arcs.push((layout.final_node(), layout.initial()));
277        arcs.push((layout.final_node(), layout.d()));
278
279        for var in 0..self.num_vars() {
280            arcs.push((layout.x_pos(var), layout.z(var)));
281            arcs.push((layout.x_neg(var), layout.z(var)));
282            arcs.push((layout.x_pos(var), layout.u(var, 0)));
283            arcs.push((layout.x_neg(var), layout.u(var, 1)));
284        }
285
286        for var in 0..self.num_vars() {
287            arcs.push((layout.w(var), layout.u(var, 0)));
288            arcs.push((layout.w(var), layout.u(var, 1)));
289        }
290
291        for var in 0..self.num_vars() {
292            for index in 0..(2 * self.num_vars() - 2 * var - 1) {
293                arcs.push((layout.x_pos(var), layout.s(var, index)));
294                arcs.push((layout.x_neg(var), layout.t(var, index)));
295            }
296            for index in 0..(2 * self.num_vars() - 2 * var) {
297                arcs.push((layout.z(var), layout.r(var, index)));
298            }
299        }
300
301        for var in 1..self.num_vars() {
302            arcs.push((layout.z(var), layout.w(var - 1)));
303            arcs.push((layout.z(var), layout.z(var - 1)));
304        }
305        if self.num_vars() > 0 {
306            let last_var = self.num_vars() - 1;
307            for clause in 0..self.num_clauses() {
308                arcs.push((layout.c(clause), layout.w(last_var)));
309                arcs.push((layout.c(clause), layout.z(last_var)));
310            }
311        }
312
313        for clause in 0..self.num_clauses() {
314            for literal_pos in 0..3 {
315                arcs.push((layout.c(clause), layout.f(clause, literal_pos)));
316            }
317        }
318
319        for index in 0..layout.b_padding {
320            arcs.push((layout.d(), layout.bnode(index)));
321        }
322        for clause in 0..self.num_clauses() {
323            arcs.push((layout.d(), layout.c(clause)));
324        }
325
326        for (clause_idx, clause) in self.clauses().iter().enumerate() {
327            let mut lit_nodes = [0usize; 3];
328            let mut neg_nodes = [0usize; 3];
329
330            for (literal_pos, &literal) in clause.literals.iter().enumerate() {
331                let var = literal.unsigned_abs() as usize - 1;
332                if literal > 0 {
333                    lit_nodes[literal_pos] = layout.x_pos(var);
334                    neg_nodes[literal_pos] = layout.x_neg(var);
335                } else {
336                    lit_nodes[literal_pos] = layout.x_neg(var);
337                    neg_nodes[literal_pos] = layout.x_pos(var);
338                }
339                arcs.push((lit_nodes[literal_pos], layout.f(clause_idx, literal_pos)));
340            }
341
342            for (earlier, &neg_node) in neg_nodes.iter().enumerate() {
343                for later in (earlier + 1)..3 {
344                    arcs.push((neg_node, layout.f(clause_idx, later)));
345                }
346            }
347        }
348
349        Reduction3SATToRegisterSufficiency {
350            target: RegisterSufficiency::new(layout.total_vertices(), arcs, layout.bound()),
351            layout,
352        }
353    }
354}
355
356#[cfg(feature = "example-db")]
357pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
358    use crate::export::SolutionPair;
359    use crate::models::formula::CNFClause;
360
361    vec![crate::example_db::specs::RuleExampleSpec {
362        id: "ksatisfiability_to_registersufficiency",
363        build: || {
364            let source = KSatisfiability::<K3>::new(
365                3,
366                vec![
367                    CNFClause::new(vec![1, -2, 3]),
368                    CNFClause::new(vec![-1, 2, -3]),
369                ],
370            );
371            let to_registers =
372                <KSatisfiability<K3> as ReduceTo<RegisterSufficiency>>::reduce_to(&source);
373
374            // Use the B&B solver on the RS instance directly, avoiding the
375            // expensive RS→ILP chain (17K vars, minutes on CI).
376            let target_config = to_registers
377                .target_problem()
378                .solve_exact()
379                .expect("satisfying 3-SAT instance must yield a feasible RS witness");
380            let source_config = to_registers.extract_solution(&target_config);
381
382            crate::example_db::specs::assemble_rule_example(
383                &source,
384                to_registers.target_problem(),
385                vec![SolutionPair {
386                    source_config,
387                    target_config,
388                }],
389            )
390        },
391    }]
392}
393
394#[cfg(test)]
395#[path = "../unit_tests/rules/ksatisfiability_registersufficiency.rs"]
396mod tests;