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 (STOC 1973), with native
4//! empty-formula/empty-clause boundary targets, compact variables, and literal
5//! repetition for nonempty short clauses. The corrected extraction
6//! rule from issue #872:
7//! - the snapshot is taken immediately after `w[n]`
8//! - `x_k = true` iff `x_pos[k]` has been computed by that snapshot
9//! - at most one of `x_pos[k]`, `x_neg[k]` can have been computed by then
10//! - the literal/clause edges keep Sethi's original orientation
11
12use crate::models::formula::KSatisfiability;
13use crate::models::misc::RegisterSufficiency;
14use crate::reduction;
15use crate::rules::traits::{ReduceTo, ReductionResult};
16use crate::variant::K3;
17use std::collections::BTreeSet;
18
19#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
20#[derive(Debug, Clone)]
21struct SethiRegisterLayout {
22    num_vars: usize,
23    num_clauses: usize,
24    b_padding: usize,
25    a_start: usize,
26    b_start: usize,
27    c_start: usize,
28    f_start: usize,
29    initial_idx: usize,
30    d_idx: usize,
31    final_idx: usize,
32    r_starts: Vec<usize>,
33    s_starts: Vec<usize>,
34    t_starts: Vec<usize>,
35    u_start: usize,
36    w_start: usize,
37    x_pos_start: usize,
38    x_neg_start: usize,
39    z_start: usize,
40    total_vertices: usize,
41    arc_capacity: usize,
42    register_bound: usize,
43}
44
45#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
46impl SethiRegisterLayout {
47    fn new(num_vars: usize, num_clauses: usize) -> Result<Self, crate::rules::ReductionError> {
48        let overflow = || {
49            crate::rules::ReductionError::integer_overflow::<KSatisfiability<K3>, RegisterSufficiency>(
50                "computing Sethi layout dimensions",
51            )
52        };
53        let twice_vars = num_vars.checked_mul(2).ok_or_else(overflow)?;
54        let square = num_vars.checked_mul(num_vars).ok_or_else(overflow)?;
55        let b_padding = twice_vars.saturating_sub(num_clauses);
56        let total_vertices = square
57            .checked_mul(3)
58            .and_then(|size| size.checked_add(num_vars.checked_mul(9)?))
59            .and_then(|size| size.checked_add(num_clauses.checked_mul(4)?))
60            .and_then(|size| size.checked_add(b_padding))
61            .and_then(|size| size.checked_add(4))
62            .ok_or_else(overflow)?;
63        let arc_capacity = square
64            .checked_mul(6)
65            .and_then(|size| size.checked_add(num_vars.checked_mul(19)?))
66            .and_then(|size| size.checked_add(num_clauses.checked_mul(16)?))
67            .and_then(|size| size.checked_add(b_padding.checked_mul(2)?))
68            .and_then(|size| size.checked_add(1))
69            .ok_or_else(overflow)?;
70        let register_bound = num_clauses
71            .checked_mul(3)
72            .and_then(|size| size.checked_add(num_vars.checked_mul(4)?))
73            .and_then(|size| size.checked_add(b_padding))
74            .and_then(|size| size.checked_add(1))
75            .ok_or_else(overflow)?;
76        // Every following prefix is a sum of nonnegative block sizes bounded
77        // by the checked total; twice_vars also bounds the per-variable indices.
78        let mut next = 0usize;
79
80        let a_start = next;
81        next += 2 * num_vars + 1;
82
83        let b_start = next;
84        next += b_padding;
85
86        let c_start = next;
87        next += num_clauses;
88
89        let f_start = next;
90        next += 3 * num_clauses;
91
92        let initial_idx = next;
93        let d_idx = next + 1;
94        let final_idx = next + 2;
95        next += 3;
96
97        let mut r_starts = Vec::with_capacity(num_vars);
98        for var in 0..num_vars {
99            r_starts.push(next);
100            next += 2 * num_vars - 2 * var;
101        }
102
103        let mut s_starts = Vec::with_capacity(num_vars);
104        for var in 0..num_vars {
105            s_starts.push(next);
106            next += 2 * num_vars - 2 * var - 1;
107        }
108
109        let mut t_starts = Vec::with_capacity(num_vars);
110        for var in 0..num_vars {
111            t_starts.push(next);
112            next += 2 * num_vars - 2 * var - 1;
113        }
114
115        let u_start = next;
116        next += 2 * num_vars;
117
118        let w_start = next;
119        next += num_vars;
120
121        let x_pos_start = next;
122        next += num_vars;
123
124        let x_neg_start = next;
125        next += num_vars;
126
127        let z_start = next;
128        next += num_vars;
129        debug_assert_eq!(next, total_vertices);
130
131        Ok(Self {
132            num_vars,
133            num_clauses,
134            b_padding,
135            a_start,
136            b_start,
137            c_start,
138            f_start,
139            initial_idx,
140            d_idx,
141            final_idx,
142            r_starts,
143            s_starts,
144            t_starts,
145            u_start,
146            w_start,
147            x_pos_start,
148            x_neg_start,
149            z_start,
150            total_vertices,
151            arc_capacity,
152            register_bound,
153        })
154    }
155
156    fn total_vertices(&self) -> usize {
157        self.total_vertices
158    }
159
160    fn bound(&self) -> usize {
161        self.register_bound
162    }
163
164    fn initial(&self) -> usize {
165        self.initial_idx
166    }
167
168    fn d(&self) -> usize {
169        self.d_idx
170    }
171
172    fn final_node(&self) -> usize {
173        self.final_idx
174    }
175
176    fn a(&self, index: usize) -> usize {
177        self.a_start + index
178    }
179
180    fn bnode(&self, index: usize) -> usize {
181        self.b_start + index
182    }
183
184    fn c(&self, clause: usize) -> usize {
185        self.c_start + clause
186    }
187
188    fn f(&self, clause: usize, literal_pos: usize) -> usize {
189        self.f_start + 3 * clause + literal_pos
190    }
191
192    fn r(&self, var: usize, index: usize) -> usize {
193        self.r_starts[var] + index
194    }
195
196    fn s(&self, var: usize, index: usize) -> usize {
197        self.s_starts[var] + index
198    }
199
200    fn t(&self, var: usize, index: usize) -> usize {
201        self.t_starts[var] + index
202    }
203
204    fn u(&self, var: usize, slot: usize) -> usize {
205        self.u_start + 2 * var + slot
206    }
207
208    fn w(&self, var: usize) -> usize {
209        self.w_start + var
210    }
211
212    fn x_pos(&self, var: usize) -> usize {
213        self.x_pos_start + var
214    }
215
216    fn x_neg(&self, var: usize) -> usize {
217        self.x_neg_start + var
218    }
219
220    fn z(&self, var: usize) -> usize {
221        self.z_start + var
222    }
223    #[cfg(any(test, feature = "example-db"))]
224    fn schedule_for_assignment(&self, assignment: &[bool]) -> Vec<usize> {
225        assert_eq!(assignment.len(), self.num_vars);
226        let mut order = Vec::with_capacity(self.total_vertices);
227        order.extend((0..2 * self.num_vars + 1).map(|i| self.a(i)));
228        order.extend((0..self.b_padding).map(|i| self.bnode(i)));
229        for clause in 0..self.num_clauses {
230            order.extend((0..3).map(|position| self.f(clause, position)));
231        }
232        for variable in 0..self.num_vars {
233            order.extend((0..2).map(|slot| self.u(variable, slot)));
234        }
235        order.push(self.initial());
236        for (variable, &positive) in assignment.iter().enumerate() {
237            order.extend((0..2 * self.num_vars - 2 * variable).map(|i| self.r(variable, i)));
238            order.push(self.z(variable));
239            order.extend((0..2 * self.num_vars - 2 * variable - 1).map(|i| {
240                if positive {
241                    self.s(variable, i)
242                } else {
243                    self.t(variable, i)
244                }
245            }));
246            order.push(if positive {
247                self.x_pos(variable)
248            } else {
249                self.x_neg(variable)
250            });
251            order.push(self.w(variable));
252        }
253        order.extend((0..self.num_clauses).map(|clause| self.c(clause)));
254        order.push(self.d());
255        for (variable, &positive) in assignment.iter().enumerate() {
256            order.extend((0..2 * self.num_vars - 2 * variable - 1).map(|i| {
257                if positive {
258                    self.t(variable, i)
259                } else {
260                    self.s(variable, i)
261                }
262            }));
263            order.push(if positive {
264                self.x_neg(variable)
265            } else {
266                self.x_pos(variable)
267            });
268        }
269        order.push(self.final_node());
270        assert_eq!(order.len(), self.total_vertices);
271        let mut positions = vec![0; self.total_vertices];
272        for (position, vertex) in order.into_iter().enumerate() {
273            positions[vertex] = position;
274        }
275        positions
276    }
277}
278
279#[derive(Debug, Clone)]
280pub struct Reduction3SATToRegisterSufficiency {
281    target: RegisterSufficiency,
282    layout: Option<SethiRegisterLayout>,
283    source_num_vars: usize,
284    source_variables: Vec<usize>,
285}
286
287impl ReductionResult for Reduction3SATToRegisterSufficiency {
288    type Source = KSatisfiability<K3>;
289    type Target = RegisterSufficiency;
290
291    fn target_problem(&self) -> &Self::Target {
292        &self.target
293    }
294
295    fn extract_solution(
296        &self,
297        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
298    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
299        let value =
300            crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
301        if !value.0 {
302            return Err(crate::rules::ExtractionError::invalid(
303                "target ordering does not satisfy the register bound and dependencies",
304            ));
305        }
306        let mut assignment = vec![false; self.source_num_vars];
307        let Some(layout) = &self.layout else {
308            // Only the empty-conjunction target has a feasible witness here.
309            return Ok(assignment);
310        };
311        let cutoff = target_solution[layout.w(layout.num_vars - 1)];
312        for (variable, &original) in self.source_variables.iter().enumerate() {
313            let positive = target_solution[layout.x_pos(variable)] < cutoff;
314            let negative = target_solution[layout.x_neg(variable)] < cutoff;
315            if positive && negative {
316                return Err(crate::rules::ExtractionError::invalid(format!(
317                    "both literals of variable {original} precede the extraction cutoff"
318                )));
319            }
320            assignment[original] = positive;
321        }
322        Ok(assignment)
323    }
324}
325
326#[reduction(
327    transform = upper_bound {
328        num_vertices = "3 * num_vars^2 + 11 * num_vars + 4 * num_clauses + 4",
329        num_arcs = "6 * num_vars^2 + 23 * num_vars + 16 * num_clauses + 1",
330        bound = "6 * num_vars + 3 * num_clauses + 1",
331        num_sinks = "1",
332    }
333)]
334impl ReduceTo<RegisterSufficiency> for KSatisfiability<K3> {
335    type Result = Reduction3SATToRegisterSufficiency;
336
337    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
338        let empty_clause = self
339            .clauses()
340            .iter()
341            .any(|clause| clause.literals.is_empty());
342        if empty_clause || self.num_clauses() == 0 {
343            // Zero vertices need zero registers (YES); one output vertex
344            // cannot be computed with zero registers (NO).
345            return Ok(Reduction3SATToRegisterSufficiency {
346                target: RegisterSufficiency::new(usize::from(empty_clause), Vec::new(), 0),
347                layout: None,
348                source_num_vars: self.num_vars(),
349                source_variables: Vec::new(),
350            });
351        }
352        let source_variables: Vec<_> = self
353            .clauses()
354            .iter()
355            .flat_map(|clause| &clause.literals)
356            .map(|literal| {
357                usize::try_from(literal.unsigned_abs())
358                    .expect("native SAT variable indices fit usize")
359                    - 1
360            })
361            .collect::<BTreeSet<_>>()
362            .into_iter()
363            .collect();
364        let clauses: Vec<_> = self
365            .clauses()
366            .iter()
367            .map(|clause| {
368                let mut literals = [0i64; 3];
369                for (position, &literal) in clause.literals.iter().enumerate() {
370                    let original = usize::try_from(literal.unsigned_abs())
371                        .expect("native SAT variable indices fit usize")
372                        - 1;
373                    let compact = source_variables
374                        .binary_search(&original)
375                        .expect("all appearing SAT variables were collected");
376                    let variable = i64::try_from(compact + 1).expect("compact SAT indices fit i64");
377                    literals[position] = if literal > 0 { variable } else { -variable };
378                }
379                // Repeating a literal preserves a nonempty disjunction. Every
380                // slot has a real literal and the original first-true lock proof.
381                for position in clause.literals.len()..3 {
382                    literals[position] = literals[0];
383                }
384                literals
385            })
386            .collect();
387        let num_vars = source_variables.len();
388        let num_clauses = clauses.len();
389        let layout = SethiRegisterLayout::new(num_vars, num_clauses)?;
390        let mut arcs = Vec::with_capacity(layout.arc_capacity);
391
392        for index in 0..(2 * num_vars + 1) {
393            arcs.push((layout.initial(), layout.a(index)));
394        }
395        for index in 0..layout.b_padding {
396            arcs.push((layout.initial(), layout.bnode(index)));
397        }
398        for clause in 0..num_clauses {
399            for literal_pos in 0..3 {
400                arcs.push((layout.initial(), layout.f(clause, literal_pos)));
401            }
402        }
403        for var in 0..num_vars {
404            arcs.push((layout.initial(), layout.u(var, 0)));
405            arcs.push((layout.initial(), layout.u(var, 1)));
406        }
407
408        for clause in 0..num_clauses {
409            arcs.push((layout.c(clause), layout.initial()));
410        }
411        for var in 0..num_vars {
412            for index in 0..(2 * num_vars - 2 * var) {
413                arcs.push((layout.r(var, index), layout.initial()));
414            }
415            for index in 0..(2 * num_vars - 2 * var - 1) {
416                arcs.push((layout.s(var, index), layout.initial()));
417                arcs.push((layout.t(var, index), layout.initial()));
418            }
419            arcs.push((layout.w(var), layout.initial()));
420        }
421
422        for var in 0..num_vars {
423            arcs.push((layout.final_node(), layout.w(var)));
424            arcs.push((layout.final_node(), layout.x_pos(var)));
425            arcs.push((layout.final_node(), layout.x_neg(var)));
426            arcs.push((layout.final_node(), layout.z(var)));
427        }
428        arcs.push((layout.final_node(), layout.initial()));
429        arcs.push((layout.final_node(), layout.d()));
430
431        for var in 0..num_vars {
432            arcs.push((layout.x_pos(var), layout.z(var)));
433            arcs.push((layout.x_neg(var), layout.z(var)));
434            arcs.push((layout.x_pos(var), layout.u(var, 0)));
435            arcs.push((layout.x_neg(var), layout.u(var, 1)));
436        }
437
438        for var in 0..num_vars {
439            arcs.push((layout.w(var), layout.u(var, 0)));
440            arcs.push((layout.w(var), layout.u(var, 1)));
441        }
442
443        for var in 0..num_vars {
444            for index in 0..(2 * num_vars - 2 * var - 1) {
445                arcs.push((layout.x_pos(var), layout.s(var, index)));
446                arcs.push((layout.x_neg(var), layout.t(var, index)));
447            }
448            for index in 0..(2 * num_vars - 2 * var) {
449                arcs.push((layout.z(var), layout.r(var, index)));
450            }
451        }
452
453        for var in 1..num_vars {
454            arcs.push((layout.z(var), layout.w(var - 1)));
455            arcs.push((layout.z(var), layout.z(var - 1)));
456        }
457        if num_vars > 0 {
458            let last_var = num_vars - 1;
459            for clause in 0..num_clauses {
460                arcs.push((layout.c(clause), layout.w(last_var)));
461                arcs.push((layout.c(clause), layout.z(last_var)));
462            }
463        }
464
465        for clause in 0..num_clauses {
466            for literal_pos in 0..3 {
467                arcs.push((layout.c(clause), layout.f(clause, literal_pos)));
468            }
469        }
470
471        for index in 0..layout.b_padding {
472            arcs.push((layout.d(), layout.bnode(index)));
473        }
474        for clause in 0..num_clauses {
475            arcs.push((layout.d(), layout.c(clause)));
476        }
477
478        for (clause_idx, clause) in clauses.iter().enumerate() {
479            let mut lit_nodes = [0usize; 3];
480            let mut neg_nodes = [0usize; 3];
481
482            for (literal_pos, &literal) in clause.iter().enumerate() {
483                let var = usize::try_from(literal.unsigned_abs())
484                    .expect("compact SAT variable indices fit usize")
485                    - 1;
486                if literal > 0 {
487                    lit_nodes[literal_pos] = layout.x_pos(var);
488                    neg_nodes[literal_pos] = layout.x_neg(var);
489                } else {
490                    lit_nodes[literal_pos] = layout.x_neg(var);
491                    neg_nodes[literal_pos] = layout.x_pos(var);
492                }
493                arcs.push((lit_nodes[literal_pos], layout.f(clause_idx, literal_pos)));
494            }
495
496            for (earlier, &neg_node) in neg_nodes.iter().enumerate() {
497                for later in (earlier + 1)..3 {
498                    arcs.push((neg_node, layout.f(clause_idx, later)));
499                }
500            }
501        }
502
503        Ok(Reduction3SATToRegisterSufficiency {
504            target: RegisterSufficiency::new(layout.total_vertices(), arcs, layout.bound()),
505            layout: Some(layout),
506            source_num_vars: self.num_vars(),
507            source_variables,
508        })
509    }
510}
511
512#[cfg(feature = "example-db")]
513pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
514    use crate::export::SolutionPair;
515    use crate::models::formula::CNFClause;
516
517    vec![crate::example_db::specs::RuleExampleSpec {
518        id: "ksatisfiability_to_registersufficiency",
519        build: || {
520            let source = KSatisfiability::<K3>::new(
521                3,
522                vec![
523                    CNFClause::new(vec![1, -2, 3]),
524                    CNFClause::new(vec![-1, 2, -3]),
525                ],
526            );
527            let to_registers =
528                <KSatisfiability<K3> as ReduceTo<RegisterSufficiency>>::reduce_to(&source)
529                    .expect("reduction should succeed");
530
531            let target_config = to_registers
532                .layout
533                .as_ref()
534                .expect("canonical formula has nonempty clauses")
535                .schedule_for_assignment(&[true, true, true]);
536            let source_config = to_registers.extract_solution(&target_config).unwrap();
537
538            crate::example_db::specs::assemble_rule_example(
539                &source,
540                to_registers.target_problem(),
541                vec![SolutionPair {
542                    source_config: serde_json::to_value(source_config)
543                        .expect("solution serialization must succeed"),
544                    target_config: serde_json::to_value(target_config)
545                        .expect("solution serialization must succeed"),
546                }],
547            )
548        },
549    }]
550}
551
552#[cfg(test)]
553#[path = "../unit_tests/rules/ksatisfiability_registersufficiency.rs"]
554mod tests;