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        crate::rules::traits::validate_target_witness(
300            self.target_problem(),
301            target_solution,
302            |value| value.0,
303            "target ordering does not satisfy the register bound and dependencies",
304        )?;
305        let mut assignment = vec![false; self.source_num_vars];
306        let Some(layout) = &self.layout else {
307            // Only the empty-conjunction target has a feasible witness here.
308            return Ok(assignment);
309        };
310        let cutoff = target_solution[layout.w(layout.num_vars - 1)];
311        for (variable, &original) in self.source_variables.iter().enumerate() {
312            let positive = target_solution[layout.x_pos(variable)] < cutoff;
313            let negative = target_solution[layout.x_neg(variable)] < cutoff;
314            if positive && negative {
315                return Err(crate::rules::ExtractionError::invalid(format!(
316                    "both literals of variable {original} precede the extraction cutoff"
317                )));
318            }
319            assignment[original] = positive;
320        }
321        Ok(assignment)
322    }
323}
324
325#[crate::aggregate_reduction(identity)]
326impl crate::rules::AggregateReductionResult for Reduction3SATToRegisterSufficiency {}
327
328#[reduction(
329    transform = upper_bound {
330        num_vertices = "3 * num_vars^2 + 11 * num_vars + 4 * num_clauses + 4",
331        num_arcs = "6 * num_vars^2 + 23 * num_vars + 16 * num_clauses + 1",
332        bound = "6 * num_vars + 3 * num_clauses + 1",
333        num_sinks = "1",
334    }
335)]
336impl ReduceTo<RegisterSufficiency> for KSatisfiability<K3> {
337    type Result = Reduction3SATToRegisterSufficiency;
338
339    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
340        let empty_clause = self
341            .clauses()
342            .iter()
343            .any(|clause| clause.literals.is_empty());
344        if empty_clause || self.num_clauses() == 0 {
345            // Zero vertices need zero registers (YES); one output vertex
346            // cannot be computed with zero registers (NO).
347            return Ok(Reduction3SATToRegisterSufficiency {
348                target: RegisterSufficiency::new(usize::from(empty_clause), Vec::new(), 0),
349                layout: None,
350                source_num_vars: self.num_vars(),
351                source_variables: Vec::new(),
352            });
353        }
354        let source_variables: Vec<_> = self
355            .clauses()
356            .iter()
357            .flat_map(|clause| &clause.literals)
358            .map(|literal| {
359                usize::try_from(literal.unsigned_abs())
360                    .expect("native SAT variable indices fit usize")
361                    - 1
362            })
363            .collect::<BTreeSet<_>>()
364            .into_iter()
365            .collect();
366        let clauses: Vec<_> = self
367            .clauses()
368            .iter()
369            .map(|clause| {
370                let mut literals = [0i64; 3];
371                for (position, &literal) in clause.literals.iter().enumerate() {
372                    let original = usize::try_from(literal.unsigned_abs())
373                        .expect("native SAT variable indices fit usize")
374                        - 1;
375                    let compact = source_variables
376                        .binary_search(&original)
377                        .expect("all appearing SAT variables were collected");
378                    let variable = i64::try_from(compact + 1).expect("compact SAT indices fit i64");
379                    literals[position] = if literal > 0 { variable } else { -variable };
380                }
381                // Repeating a literal preserves a nonempty disjunction. Every
382                // slot has a real literal and the original first-true lock proof.
383                for position in clause.literals.len()..3 {
384                    literals[position] = literals[0];
385                }
386                literals
387            })
388            .collect();
389        let num_vars = source_variables.len();
390        let num_clauses = clauses.len();
391        let layout = SethiRegisterLayout::new(num_vars, num_clauses)?;
392        let mut arcs = Vec::with_capacity(layout.arc_capacity);
393
394        for index in 0..(2 * num_vars + 1) {
395            arcs.push((layout.initial(), layout.a(index)));
396        }
397        for index in 0..layout.b_padding {
398            arcs.push((layout.initial(), layout.bnode(index)));
399        }
400        for clause in 0..num_clauses {
401            for literal_pos in 0..3 {
402                arcs.push((layout.initial(), layout.f(clause, literal_pos)));
403            }
404        }
405        for var in 0..num_vars {
406            arcs.push((layout.initial(), layout.u(var, 0)));
407            arcs.push((layout.initial(), layout.u(var, 1)));
408        }
409
410        for clause in 0..num_clauses {
411            arcs.push((layout.c(clause), layout.initial()));
412        }
413        for var in 0..num_vars {
414            for index in 0..(2 * num_vars - 2 * var) {
415                arcs.push((layout.r(var, index), layout.initial()));
416            }
417            for index in 0..(2 * num_vars - 2 * var - 1) {
418                arcs.push((layout.s(var, index), layout.initial()));
419                arcs.push((layout.t(var, index), layout.initial()));
420            }
421            arcs.push((layout.w(var), layout.initial()));
422        }
423
424        for var in 0..num_vars {
425            arcs.push((layout.final_node(), layout.w(var)));
426            arcs.push((layout.final_node(), layout.x_pos(var)));
427            arcs.push((layout.final_node(), layout.x_neg(var)));
428            arcs.push((layout.final_node(), layout.z(var)));
429        }
430        arcs.push((layout.final_node(), layout.initial()));
431        arcs.push((layout.final_node(), layout.d()));
432
433        for var in 0..num_vars {
434            arcs.push((layout.x_pos(var), layout.z(var)));
435            arcs.push((layout.x_neg(var), layout.z(var)));
436            arcs.push((layout.x_pos(var), layout.u(var, 0)));
437            arcs.push((layout.x_neg(var), layout.u(var, 1)));
438        }
439
440        for var in 0..num_vars {
441            arcs.push((layout.w(var), layout.u(var, 0)));
442            arcs.push((layout.w(var), layout.u(var, 1)));
443        }
444
445        for var in 0..num_vars {
446            for index in 0..(2 * num_vars - 2 * var - 1) {
447                arcs.push((layout.x_pos(var), layout.s(var, index)));
448                arcs.push((layout.x_neg(var), layout.t(var, index)));
449            }
450            for index in 0..(2 * num_vars - 2 * var) {
451                arcs.push((layout.z(var), layout.r(var, index)));
452            }
453        }
454
455        for var in 1..num_vars {
456            arcs.push((layout.z(var), layout.w(var - 1)));
457            arcs.push((layout.z(var), layout.z(var - 1)));
458        }
459        if num_vars > 0 {
460            let last_var = num_vars - 1;
461            for clause in 0..num_clauses {
462                arcs.push((layout.c(clause), layout.w(last_var)));
463                arcs.push((layout.c(clause), layout.z(last_var)));
464            }
465        }
466
467        for clause in 0..num_clauses {
468            for literal_pos in 0..3 {
469                arcs.push((layout.c(clause), layout.f(clause, literal_pos)));
470            }
471        }
472
473        for index in 0..layout.b_padding {
474            arcs.push((layout.d(), layout.bnode(index)));
475        }
476        for clause in 0..num_clauses {
477            arcs.push((layout.d(), layout.c(clause)));
478        }
479
480        for (clause_idx, clause) in clauses.iter().enumerate() {
481            let mut lit_nodes = [0usize; 3];
482            let mut neg_nodes = [0usize; 3];
483
484            for (literal_pos, &literal) in clause.iter().enumerate() {
485                let var = usize::try_from(literal.unsigned_abs())
486                    .expect("compact SAT variable indices fit usize")
487                    - 1;
488                if literal > 0 {
489                    lit_nodes[literal_pos] = layout.x_pos(var);
490                    neg_nodes[literal_pos] = layout.x_neg(var);
491                } else {
492                    lit_nodes[literal_pos] = layout.x_neg(var);
493                    neg_nodes[literal_pos] = layout.x_pos(var);
494                }
495                arcs.push((lit_nodes[literal_pos], layout.f(clause_idx, literal_pos)));
496            }
497
498            for (earlier, &neg_node) in neg_nodes.iter().enumerate() {
499                for later in (earlier + 1)..3 {
500                    arcs.push((neg_node, layout.f(clause_idx, later)));
501                }
502            }
503        }
504
505        Ok(Reduction3SATToRegisterSufficiency {
506            target: RegisterSufficiency::new(layout.total_vertices(), arcs, layout.bound()),
507            layout: Some(layout),
508            source_num_vars: self.num_vars(),
509            source_variables,
510        })
511    }
512}
513
514#[cfg(feature = "example-db")]
515pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
516    use crate::export::SolutionPair;
517    use crate::models::formula::CNFClause;
518
519    vec![crate::example_db::specs::RuleExampleSpec {
520        id: "ksatisfiability_to_registersufficiency",
521        build: || {
522            let source = KSatisfiability::<K3>::new(
523                3,
524                vec![
525                    CNFClause::new(vec![1, -2, 3]),
526                    CNFClause::new(vec![-1, 2, -3]),
527                ],
528            );
529            let to_registers =
530                <KSatisfiability<K3> as ReduceTo<RegisterSufficiency>>::reduce_to(&source)
531                    .expect("reduction should succeed");
532
533            let target_config = to_registers
534                .layout
535                .as_ref()
536                .expect("canonical formula has nonempty clauses")
537                .schedule_for_assignment(&[true, true, true]);
538            let source_config = to_registers.extract_solution(&target_config).unwrap();
539
540            crate::example_db::specs::assemble_rule_example(
541                &source,
542                to_registers.target_problem(),
543                vec![SolutionPair {
544                    source_config: serde_json::to_value(source_config)
545                        .expect("solution serialization must succeed"),
546                    target_config: serde_json::to_value(target_config)
547                        .expect("solution serialization must succeed"),
548                }],
549            )
550        },
551    }]
552}
553
554#[cfg(test)]
555#[path = "../unit_tests/rules/ksatisfiability_registersufficiency.rs"]
556mod tests;