problemreductions/rules/
sat_maximumindependentset.rs

1//! Reduction from Satisfiability (SAT) to MaximumIndependentSet.
2//!
3//! The reduction creates one vertex for each literal occurrence in each clause.
4//! Edges are added:
5//! 1. Between all literals within the same clause (forming a clique per clause)
6//! 2. Between complementary literals (x and NOT x) across different clauses
7//!
8//! A satisfying assignment corresponds to an independent set of size = num_clauses,
9//! where we pick exactly one literal from each clause.
10
11use crate::models::formula::Satisfiability;
12use crate::models::graph::MaximumIndependentSet;
13use crate::reduction;
14use crate::rules::traits::{ReduceTo, ReductionResult};
15use crate::topology::SimpleGraph;
16use crate::types::One;
17
18/// A literal in the SAT problem, representing a variable or its negation.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct BoolVar {
21    /// The variable name/index (0-indexed).
22    pub name: usize,
23    /// Whether this literal is negated.
24    pub neg: bool,
25}
26
27impl BoolVar {
28    /// Create a new literal.
29    pub fn new(name: usize, neg: bool) -> Self {
30        Self { name, neg }
31    }
32
33    /// Create a literal from a signed integer (1-indexed, as in DIMACS format).
34    /// Positive means the variable, negative means its negation.
35    pub fn from_literal(lit: i32) -> Self {
36        let name = lit.unsigned_abs() as usize - 1; // Convert to 0-indexed
37        let neg = lit < 0;
38        Self { name, neg }
39    }
40
41    /// Check if this literal is the complement of another.
42    pub fn is_complement(&self, other: &BoolVar) -> bool {
43        self.name == other.name && self.neg != other.neg
44    }
45}
46
47/// Result of reducing Satisfiability to MaximumIndependentSet.
48///
49/// This struct contains:
50/// - The target MaximumIndependentSet problem
51/// - A mapping from vertex indices to literals
52/// - The list of source variable indices
53/// - The number of clauses in the original SAT problem
54#[derive(Debug, Clone)]
55pub struct ReductionSATToIS {
56    /// The target MaximumIndependentSet problem.
57    target: MaximumIndependentSet<SimpleGraph, One>,
58    /// Mapping from vertex index to the literal it represents.
59    literals: Vec<BoolVar>,
60    /// The number of variables in the source SAT problem.
61    num_source_variables: usize,
62    /// The number of clauses in the source SAT problem.
63    num_clauses: usize,
64}
65
66impl ReductionResult for ReductionSATToIS {
67    type Source = Satisfiability;
68    type Target = MaximumIndependentSet<SimpleGraph, One>;
69
70    fn target_problem(&self) -> &Self::Target {
71        &self.target
72    }
73
74    /// Extract a SAT solution from an MaximumIndependentSet solution.
75    ///
76    /// For each selected vertex (representing a literal), we set the corresponding
77    /// variable to make that literal true. Variables not covered by any selected
78    /// literal default to false.
79    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
80        let mut assignment = vec![0usize; self.num_source_variables];
81        let mut covered = vec![false; self.num_source_variables];
82
83        for (vertex_idx, &selected) in target_solution.iter().enumerate() {
84            if selected == 1 {
85                let literal = &self.literals[vertex_idx];
86                // If the literal is positive (neg=false), variable should be true (1)
87                // If the literal is negated (neg=true), variable should be false (0)
88                assignment[literal.name] = if literal.neg { 0 } else { 1 };
89                covered[literal.name] = true;
90            }
91        }
92
93        // Variables not covered can be assigned any value (we use 0)
94        // They are already initialized to 0
95        assignment
96    }
97}
98
99impl ReductionSATToIS {
100    /// Get the number of clauses in the source SAT problem.
101    pub fn num_clauses(&self) -> usize {
102        self.num_clauses
103    }
104
105    /// Get a reference to the literals mapping.
106    pub fn literals(&self) -> &[BoolVar] {
107        &self.literals
108    }
109}
110
111#[reduction(
112    overhead = {
113        num_vertices = "num_literals",
114        num_edges = "num_literals^2",
115    }
116)]
117impl ReduceTo<MaximumIndependentSet<SimpleGraph, One>> for Satisfiability {
118    type Result = ReductionSATToIS;
119
120    fn reduce_to(&self) -> Self::Result {
121        let mut literals: Vec<BoolVar> = Vec::new();
122        let mut edges: Vec<(usize, usize)> = Vec::new();
123        let mut vertex_count = 0;
124
125        // First pass: add vertices for each literal in each clause
126        // and add clique edges within each clause
127        for clause in self.clauses() {
128            let clause_start = vertex_count;
129
130            // Add vertices for each literal in this clause
131            for &lit in &clause.literals {
132                literals.push(BoolVar::from_literal(lit));
133                vertex_count += 1;
134            }
135
136            // Add clique edges within this clause
137            for i in clause_start..vertex_count {
138                for j in (i + 1)..vertex_count {
139                    edges.push((i, j));
140                }
141            }
142        }
143
144        // Second pass: add edges between complementary literals across clauses
145        // Since we only add clique edges within clauses in the first pass,
146        // complementary literals in different clauses won't already have an edge
147        for i in 0..vertex_count {
148            for j in (i + 1)..vertex_count {
149                if literals[i].is_complement(&literals[j]) {
150                    edges.push((i, j));
151                }
152            }
153        }
154
155        let target = MaximumIndependentSet::new(
156            SimpleGraph::new(vertex_count, edges),
157            vec![One; vertex_count],
158        );
159
160        ReductionSATToIS {
161            target,
162            literals,
163            num_source_variables: self.num_vars(),
164            num_clauses: self.num_clauses(),
165        }
166    }
167}
168
169#[cfg(test)]
170#[path = "../unit_tests/rules/sat_maximumindependentset.rs"]
171mod tests;