oxiz_sat/parallel/
clause_simplify.rs1use crate::Clause;
6#[allow(unused_imports)]
7use crate::prelude::*;
8use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
9
10#[derive(Debug, Clone)]
12pub struct SimplificationConfig {
13 pub enable_subsumption: bool,
15 pub enable_self_subsuming: bool,
17 pub enable_duplicate_removal: bool,
19 pub enable_tautology_removal: bool,
21 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#[derive(Debug, Clone)]
39pub struct SimplificationResult {
40 pub clauses: Vec<Clause>,
42 pub subsumed_count: usize,
44 pub tautology_count: usize,
46 pub duplicate_count: usize,
48}
49
50pub struct ParallelClauseSimplifier {
52 config: SimplificationConfig,
53}
54
55impl ParallelClauseSimplifier {
56 pub fn new(config: SimplificationConfig) -> Self {
58 Self { config }
59 }
60
61 pub fn default_config() -> Self {
63 Self::new(SimplificationConfig::default())
64 }
65
66 pub fn simplify(&self, clauses: &[Clause]) -> SimplificationResult {
68 let mut working_clauses = clauses.to_vec();
69 let mut stats = SimplificationStats::default();
70
71 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 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 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 let before_dedup = cleaned.len();
118 let mut seen = FxHashSet::default();
119 let mut deduplicated = Vec::new();
120
121 for clause in cleaned {
122 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 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 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 fn remove_subsumed(&self, clauses: &[Clause]) -> (Vec<Clause>, usize) {
157 let mut non_subsumed: Vec<Clause> = Vec::new();
159 let mut subsumed_count = 0;
160
161 let subsumption_flags: Vec<_> = clauses
163 .par_iter()
164 .enumerate()
165 .map(|(i, clause)| {
166 for (j, other) in clauses.iter().enumerate() {
168 if i != j && self.subsumes(other, clause) {
169 return true; }
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 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 pub fn self_subsuming_resolution(&self, clauses: &[Clause]) -> Vec<Clause> {
206 clauses
208 .par_iter()
209 .map(|clause: &Clause| -> Clause {
210 clause.clone()
213 })
214 .collect()
215 }
216}
217
218#[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)); assert!(!simplifier.subsumes(&b, &a)); }
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]), make_clause(vec![1, -1]), ];
267
268 let result = simplifier.simplify(&clauses);
269 assert!(result.tautology_count > 0);
270 assert!(result.subsumed_count > 0);
271 }
272}