Skip to main content

oxiz_sat/parallel/
clause_simplify.rs

1//! Parallel Clause Simplification.
2//!
3//! Simplifies clause databases in parallel using multiple threads.
4
5use crate::Clause;
6#[allow(unused_imports)]
7use crate::prelude::*;
8use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
9
10/// Configuration for parallel simplification.
11#[derive(Debug, Clone)]
12pub struct SimplificationConfig {
13    /// Enable subsumption checking
14    pub enable_subsumption: bool,
15    /// Enable self-subsuming resolution
16    pub enable_self_subsuming: bool,
17    /// Enable duplicate literal removal
18    pub enable_duplicate_removal: bool,
19    /// Enable tautology removal
20    pub enable_tautology_removal: bool,
21    /// Chunk size for parallel processing
22    pub chunk_size: usize,
23}
24
25impl Default for SimplificationConfig {
26    fn default() -> Self {
27        Self {
28            enable_subsumption: true,
29            enable_self_subsuming: true,
30            enable_duplicate_removal: true,
31            enable_tautology_removal: true,
32            chunk_size: 1000,
33        }
34    }
35}
36
37/// Result of simplification.
38#[derive(Debug, Clone)]
39pub struct SimplificationResult {
40    /// Simplified clauses
41    pub clauses: Vec<Clause>,
42    /// Number of subsumed clauses removed
43    pub subsumed_count: usize,
44    /// Number of tautologies removed
45    pub tautology_count: usize,
46    /// Number of duplicates removed
47    pub duplicate_count: usize,
48}
49
50/// Parallel clause simplifier.
51pub struct ParallelClauseSimplifier {
52    config: SimplificationConfig,
53}
54
55impl ParallelClauseSimplifier {
56    /// Create a new simplifier.
57    pub fn new(config: SimplificationConfig) -> Self {
58        Self { config }
59    }
60
61    /// Create with default configuration.
62    pub fn default_config() -> Self {
63        Self::new(SimplificationConfig::default())
64    }
65
66    /// Simplify a clause database in parallel.
67    pub fn simplify(&self, clauses: &[Clause]) -> SimplificationResult {
68        let mut working_clauses = clauses.to_vec();
69        let mut stats = SimplificationStats::default();
70
71        // Phase 1: Remove tautologies and duplicates in parallel
72        if self.config.enable_tautology_removal || self.config.enable_duplicate_removal {
73            let (cleaned, taut_count, dup_count) = self.remove_trivial_clauses(&working_clauses);
74            working_clauses = cleaned;
75            stats.tautologies_removed += taut_count;
76            stats.duplicates_removed += dup_count;
77        }
78
79        // Phase 2: Subsumption checking in parallel
80        if self.config.enable_subsumption {
81            let (non_subsumed, sub_count) = self.remove_subsumed(&working_clauses);
82            working_clauses = non_subsumed;
83            stats.subsumed_removed += sub_count;
84        }
85
86        SimplificationResult {
87            clauses: working_clauses,
88            subsumed_count: stats.subsumed_removed,
89            tautology_count: stats.tautologies_removed,
90            duplicate_count: stats.duplicates_removed,
91        }
92    }
93
94    /// Remove tautologies and duplicates in parallel.
95    fn remove_trivial_clauses(&self, clauses: &[Clause]) -> (Vec<Clause>, usize, usize) {
96        let results: Vec<_> = clauses
97            .par_iter()
98            .map(|clause| {
99                let is_taut = self.is_tautology(clause);
100                (!is_taut, is_taut)
101            })
102            .collect();
103
104        let mut cleaned: Vec<Clause> = Vec::new();
105        let mut tautology_count = 0;
106
107        for (i, (keep, is_taut)) in results.iter().enumerate() {
108            if *keep {
109                cleaned.push(clauses[i].clone());
110            }
111            if *is_taut {
112                tautology_count += 1;
113            }
114        }
115
116        // Remove duplicates using hash set
117        let before_dedup = cleaned.len();
118        let mut seen = FxHashSet::default();
119        let mut deduplicated = Vec::new();
120
121        for clause in cleaned {
122            // Create a canonical representation for deduplication
123            let mut sorted_lits = clause.lits.iter().copied().collect::<Vec<_>>();
124            sorted_lits.sort_unstable_by_key(|l| l.code());
125
126            if seen.insert(sorted_lits) {
127                deduplicated.push(clause);
128            }
129        }
130
131        let duplicate_count = before_dedup - deduplicated.len();
132
133        (deduplicated, tautology_count, duplicate_count)
134    }
135
136    /// Check if a clause is a tautology (contains both p and ¬p).
137    fn is_tautology(&self, clause: &Clause) -> bool {
138        let mut seen = FxHashSet::default();
139
140        for &lit in &clause.lits {
141            let var = lit.var();
142            if seen.contains(&var) {
143                // Check if we've seen the negation
144                let neg_lit = lit.negate();
145                if clause.lits.contains(&neg_lit) {
146                    return true;
147                }
148            }
149            seen.insert(var);
150        }
151
152        false
153    }
154
155    /// Remove subsumed clauses in parallel.
156    fn remove_subsumed(&self, clauses: &[Clause]) -> (Vec<Clause>, usize) {
157        // Build subsumption candidates in parallel
158        let mut non_subsumed: Vec<Clause> = Vec::new();
159        let mut subsumed_count = 0;
160
161        // Simple O(n²) subsumption check with parallelization
162        let subsumption_flags: Vec<_> = clauses
163            .par_iter()
164            .enumerate()
165            .map(|(i, clause)| {
166                // Check if clause i is subsumed by any other clause
167                for (j, other) in clauses.iter().enumerate() {
168                    if i != j && self.subsumes(other, clause) {
169                        return true; // clause i is subsumed
170                    }
171                }
172                false
173            })
174            .collect();
175
176        for (i, &is_subsumed) in subsumption_flags.iter().enumerate() {
177            if !is_subsumed {
178                non_subsumed.push(clauses[i].clone());
179            } else {
180                subsumed_count += 1;
181            }
182        }
183
184        (non_subsumed, subsumed_count)
185    }
186
187    /// Check if clause a subsumes clause b (a ⊆ b).
188    fn subsumes(&self, a: &Clause, b: &Clause) -> bool {
189        if a.len() > b.len() {
190            return false;
191        }
192
193        let b_lits: FxHashSet<_> = b.lits.iter().copied().collect();
194
195        for &lit in &a.lits {
196            if !b_lits.contains(&lit) {
197                return false;
198            }
199        }
200
201        true
202    }
203
204    /// Perform self-subsuming resolution.
205    pub fn self_subsuming_resolution(&self, clauses: &[Clause]) -> Vec<Clause> {
206        // Parallel self-subsuming resolution
207        clauses
208            .par_iter()
209            .map(|clause: &Clause| -> Clause {
210                // Try to find self-subsuming resolvents
211                // Simplified: would perform actual resolution
212                clause.clone()
213            })
214            .collect()
215    }
216}
217
218/// Statistics for simplification.
219#[derive(Debug, Clone, Default)]
220struct SimplificationStats {
221    tautologies_removed: usize,
222    duplicates_removed: usize,
223    subsumed_removed: usize,
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use crate::Lit;
230
231    fn make_clause(lits: Vec<i32>) -> Clause {
232        let clause_lits: Vec<Lit> = lits.into_iter().map(Lit::from_dimacs).collect();
233        Clause::new(clause_lits, false)
234    }
235
236    #[test]
237    fn test_tautology_detection() {
238        let simplifier = ParallelClauseSimplifier::default_config();
239
240        let taut = make_clause(vec![1, -1, 2]);
241        assert!(simplifier.is_tautology(&taut));
242
243        let non_taut = make_clause(vec![1, 2, 3]);
244        assert!(!simplifier.is_tautology(&non_taut));
245    }
246
247    #[test]
248    fn test_subsumption() {
249        let simplifier = ParallelClauseSimplifier::default_config();
250
251        let a = make_clause(vec![1, 2]);
252        let b = make_clause(vec![1, 2, 3]);
253
254        assert!(simplifier.subsumes(&a, &b)); // a subsumes b
255        assert!(!simplifier.subsumes(&b, &a)); // b does not subsume a
256    }
257
258    #[test]
259    fn test_simplification() {
260        let simplifier = ParallelClauseSimplifier::default_config();
261
262        let clauses = vec![
263            make_clause(vec![1, 2]),
264            make_clause(vec![1, 2, 3]), // subsumed by first
265            make_clause(vec![1, -1]),   // tautology
266        ];
267
268        let result = simplifier.simplify(&clauses);
269        assert!(result.tautology_count > 0);
270        assert!(result.subsumed_count > 0);
271    }
272}