oxiz_sat/
preprocessing_core.rs1use crate::clause::{ClauseDatabase, ClauseId};
11use crate::literal::{Lit, Var};
12#[allow(unused_imports)]
13use crate::prelude::*;
14
15#[derive(Debug, Clone)]
17struct OccurrenceList {
18 occurrences: Vec<Vec<ClauseId>>,
20}
21
22impl OccurrenceList {
23 fn new(num_vars: usize) -> Self {
24 Self {
25 occurrences: vec![Vec::new(); num_vars * 2],
26 }
27 }
28
29 fn add(&mut self, lit: Lit, clause_id: ClauseId) {
30 self.occurrences[lit.code() as usize].push(clause_id);
31 }
32
33 fn get(&self, lit: Lit) -> &[ClauseId] {
34 &self.occurrences[lit.code() as usize]
35 }
36
37 fn remove(&mut self, lit: Lit, clause_id: ClauseId) {
38 let list = &mut self.occurrences[lit.code() as usize];
39 if let Some(pos) = list.iter().position(|&id| id == clause_id) {
40 list.swap_remove(pos);
41 }
42 }
43
44 fn clear(&mut self) {
45 for list in &mut self.occurrences {
46 list.clear();
47 }
48 }
49}
50
51#[derive(Debug)]
53pub struct Preprocessor {
54 num_vars: usize,
56 occurrences: OccurrenceList,
58 eliminated: HashSet<Var>,
60 removed_clauses: HashSet<ClauseId>,
62}
63
64impl Preprocessor {
65 pub fn new(num_vars: usize) -> Self {
67 Self {
68 num_vars,
69 occurrences: OccurrenceList::new(num_vars),
70 eliminated: HashSet::new(),
71 removed_clauses: HashSet::new(),
72 }
73 }
74
75 pub fn build_occurrences(&mut self, clauses: &ClauseDatabase) {
77 self.occurrences.clear();
78 for i in 0..clauses.len() {
79 let clause_id = ClauseId::new(i as u32);
80 if let Some(clause) = clauses.get(clause_id)
81 && !clause.deleted
82 {
83 for &lit in &clause.lits {
84 self.occurrences.add(lit, clause_id);
85 }
86 }
87 }
88 }
89
90 fn is_tautology(lits: &[Lit]) -> bool {
92 let mut seen = HashSet::new();
93 for &lit in lits {
94 if seen.contains(&lit.negate()) {
95 return true;
96 }
97 seen.insert(lit);
98 }
99 false
100 }
101
102 fn is_blocked(&self, clause_lits: &[Lit], blocking_lit: Lit, clauses: &ClauseDatabase) -> bool {
107 let neg_lit = blocking_lit.negate();
109
110 for &other_clause_id in self.occurrences.get(neg_lit) {
111 if let Some(other_clause) = clauses.get(other_clause_id) {
112 if other_clause.deleted {
113 continue;
114 }
115
116 let mut resolvent = Vec::new();
118
119 for &lit in clause_lits {
121 if lit != blocking_lit {
122 resolvent.push(lit);
123 }
124 }
125
126 for &lit in &other_clause.lits {
128 if lit != neg_lit {
129 resolvent.push(lit);
130 }
131 }
132
133 if !Self::is_tautology(&resolvent) {
135 return false;
136 }
137 }
138 }
139
140 true
141 }
142
143 pub fn blocked_clause_elimination(&mut self, clauses: &mut ClauseDatabase) -> usize {
148 let mut eliminated = 0;
149 self.build_occurrences(clauses);
150
151 let clause_ids: Vec<_> = (0..clauses.len())
153 .map(|i| ClauseId::new(i as u32))
154 .collect();
155
156 for clause_id in clause_ids {
157 if self.removed_clauses.contains(&clause_id) {
158 continue;
159 }
160
161 if let Some(clause) = clauses.get(clause_id) {
162 if clause.deleted || clause.learned {
163 continue;
164 }
165
166 let lits = clause.lits.clone();
167
168 for &lit in &lits {
170 if self.is_blocked(&lits, lit, clauses) {
171 if let Some(clause) = clauses.get_mut(clause_id) {
173 clause.deleted = true;
174 }
175 self.removed_clauses.insert(clause_id);
176 eliminated += 1;
177
178 for &l in &lits {
180 self.occurrences.remove(l, clause_id);
181 }
182 break;
183 }
184 }
185 }
186 }
187
188 eliminated
189 }
190
191 pub fn pure_literal_elimination(&mut self, clauses: &mut ClauseDatabase) -> usize {
197 let mut eliminated = 0;
198 self.build_occurrences(clauses);
199
200 let mut pure_literals = Vec::new();
202
203 for v in 0..self.num_vars {
204 let var = Var(v as u32);
205 let pos_lit = Lit::pos(var);
206 let neg_lit = Lit::neg(var);
207
208 let pos_occurs = !self.occurrences.get(pos_lit).is_empty();
209 let neg_occurs = !self.occurrences.get(neg_lit).is_empty();
210
211 if pos_occurs && !neg_occurs {
212 pure_literals.push(pos_lit);
213 } else if neg_occurs && !pos_occurs {
214 pure_literals.push(neg_lit);
215 }
216 }
217
218 for lit in pure_literals {
220 for &clause_id in self.occurrences.get(lit).iter() {
221 if !self.removed_clauses.contains(&clause_id)
222 && let Some(clause) = clauses.get_mut(clause_id)
223 && !clause.deleted
224 && !clause.learned
225 {
226 clause.deleted = true;
227 self.removed_clauses.insert(clause_id);
228 eliminated += 1;
229 }
230 }
231 }
232
233 eliminated
234 }
235
236 pub fn subsumption_elimination(&mut self, clauses: &mut ClauseDatabase) -> usize {
242 let mut eliminated = 0;
243 self.build_occurrences(clauses);
244
245 let clause_ids: Vec<_> = (0..clauses.len())
246 .map(|i| ClauseId::new(i as u32))
247 .collect();
248
249 for i in 0..clause_ids.len() {
250 let clause_id = clause_ids[i];
251
252 if self.removed_clauses.contains(&clause_id) {
253 continue;
254 }
255
256 let clause_lits = if let Some(clause) = clauses.get(clause_id) {
257 if clause.deleted || clause.learned {
258 continue;
259 }
260 clause.lits.clone()
261 } else {
262 continue;
263 };
264
265 for &other_id in &clause_ids[(i + 1)..] {
267 if self.removed_clauses.contains(&other_id) {
268 continue;
269 }
270
271 let other_lits = if let Some(other_clause) = clauses.get(other_id) {
272 if other_clause.deleted || other_clause.learned {
273 continue;
274 }
275 &other_clause.lits
276 } else {
277 continue;
278 };
279
280 if clause_lits.iter().all(|lit| other_lits.contains(lit)) {
282 if let Some(other_clause) = clauses.get_mut(other_id) {
284 other_clause.deleted = true;
285 }
286 self.removed_clauses.insert(other_id);
287 eliminated += 1;
288 }
289 }
290 }
291
292 eliminated
293 }
294
295 #[allow(dead_code)]
301 pub fn variable_elimination(&mut self, clauses: &mut ClauseDatabase, limit: usize) -> usize {
302 let mut eliminated = 0;
303 self.build_occurrences(clauses);
304
305 for v in 0..self.num_vars {
306 let var = Var(v as u32);
307 if self.eliminated.contains(&var) {
308 continue;
309 }
310
311 let pos_lit = Lit::pos(var);
312 let neg_lit = Lit::neg(var);
313
314 let pos_clauses: Vec<_> = self.occurrences.get(pos_lit).to_vec();
315 let neg_clauses: Vec<_> = self.occurrences.get(neg_lit).to_vec();
316
317 let resolvents = pos_clauses.len() * neg_clauses.len();
319 let current = pos_clauses.len() + neg_clauses.len();
320
321 if resolvents > limit || resolvents > current {
322 continue;
323 }
324
325 let mut new_clauses = Vec::new();
327
328 for &pos_clause_id in &pos_clauses {
329 for &neg_clause_id in &neg_clauses {
330 let pos_lits = if let Some(c) = clauses.get(pos_clause_id) {
331 &c.lits
332 } else {
333 continue;
334 };
335
336 let neg_lits = if let Some(c) = clauses.get(neg_clause_id) {
337 &c.lits
338 } else {
339 continue;
340 };
341
342 let mut resolvent = Vec::new();
344
345 for &lit in pos_lits {
346 if lit != pos_lit {
347 resolvent.push(lit);
348 }
349 }
350
351 for &lit in neg_lits {
352 if lit != neg_lit && !resolvent.contains(&lit) {
353 resolvent.push(lit);
354 }
355 }
356
357 if Self::is_tautology(&resolvent) {
359 continue;
360 }
361
362 new_clauses.push(resolvent);
363 }
364 }
365
366 self.eliminated.insert(var);
368 eliminated += 1;
369
370 for &clause_id in &pos_clauses {
372 if let Some(clause) = clauses.get_mut(clause_id) {
373 clause.deleted = true;
374 }
375 self.removed_clauses.insert(clause_id);
376 }
377
378 for &clause_id in &neg_clauses {
379 if let Some(clause) = clauses.get_mut(clause_id) {
380 clause.deleted = true;
381 }
382 self.removed_clauses.insert(clause_id);
383 }
384
385 for resolvent in new_clauses {
387 if !resolvent.is_empty() {
388 clauses.add_original(resolvent);
389 }
390 }
391 }
392
393 eliminated
394 }
395
396 pub fn failed_literal_probing(&mut self, clauses: &mut ClauseDatabase) -> usize {
402 use crate::trail::Trail;
403 use crate::watched::{WatchLists, Watcher};
404
405 let mut found = 0;
406 self.build_occurrences(clauses);
407
408 let mut trail = Trail::new(self.num_vars);
410 let mut watches = WatchLists::new(self.num_vars);
411
412 for i in 0..clauses.len() {
414 let clause_id = ClauseId::new(i as u32);
415 if let Some(clause) = clauses.get(clause_id) {
416 if clause.deleted || clause.lits.len() < 2 {
417 continue;
418 }
419
420 let lit0 = clause.lits[0];
421 let lit1 = clause.lits[1];
422 watches.add(lit0.negate(), Watcher::new(clause_id, lit1));
423 watches.add(lit1.negate(), Watcher::new(clause_id, lit0));
424 }
425 }
426
427 let mut failed_literals = Vec::new();
429
430 for v in 0..self.num_vars {
431 let var = Var(v as u32);
432 if trail.is_assigned(var) {
433 continue;
434 }
435
436 for &polarity in &[false, true] {
437 let probe_lit = if polarity {
438 Lit::pos(var)
439 } else {
440 Lit::neg(var)
441 };
442
443 let saved_level = trail.decision_level();
445
446 trail.new_decision_level();
448 trail.assign_decision(probe_lit);
449
450 let conflict = self.propagate_probe(&mut trail, &watches, clauses);
452
453 trail.backtrack_to(saved_level);
455
456 if conflict {
457 failed_literals.push(probe_lit.negate());
459 found += 1;
460 break;
461 }
462 }
463 }
464
465 for lit in failed_literals {
467 clauses.add_original([lit]);
468 }
469
470 found
471 }
472
473 fn propagate_probe(
475 &self,
476 trail: &mut crate::trail::Trail,
477 watches: &crate::watched::WatchLists,
478 clauses: &ClauseDatabase,
479 ) -> bool {
480 use crate::literal::LBool;
481
482 while let Some(lit) = trail.next_to_propagate() {
483 let watch_list = watches.get(lit);
484
485 for &watcher in watch_list {
486 let clause_id = watcher.clause;
487 let blocker = watcher.blocker;
488
489 if trail.lit_value(blocker) == LBool::True {
491 continue;
492 }
493
494 let clause = match clauses.get(clause_id) {
495 Some(c) if !c.deleted => c,
496 _ => continue,
497 };
498
499 let mut first = clause.lits[0];
501 let mut second = clause.lits[1];
502
503 if first == lit.negate() {
504 core::mem::swap(&mut first, &mut second);
505 }
506
507 let mut found_new_watch = false;
509 for &other_lit in &clause.lits[2..] {
510 if trail.lit_value(other_lit) != LBool::False {
511 found_new_watch = true;
513 break;
514 }
515 }
516
517 if !found_new_watch {
518 if trail.lit_value(first) == LBool::False {
520 return true; }
522
523 if !trail.is_assigned(first.var()) {
525 trail.assign_propagation(first, clause_id);
526 }
527 }
528 }
529 }
530
531 false }
533
534 #[allow(dead_code)]
543 pub fn bounded_variable_addition(
544 &mut self,
545 clauses: &mut ClauseDatabase,
546 max_vars_to_add: usize,
547 ) -> usize {
548 let mut vars_added = 0;
549 self.build_occurrences(clauses);
550
551 let clause_ids: Vec<_> = (0..clauses.len())
553 .map(|i| ClauseId::new(i as u32))
554 .filter(|&id| {
555 clauses
556 .get(id)
557 .is_some_and(|c| !c.deleted && !c.learned && c.lits.len() >= 3)
558 })
559 .collect();
560
561 for i in 0..clause_ids.len() {
562 if vars_added >= max_vars_to_add {
563 break;
564 }
565
566 let clause1_id = clause_ids[i];
567 let clause1_lits = match clauses.get(clause1_id) {
568 Some(c) if !c.deleted => c.lits.clone(),
569 _ => continue,
570 };
571
572 for &clause2_id in &clause_ids[(i + 1)..] {
573 if vars_added >= max_vars_to_add {
574 break;
575 }
576
577 let clause2_lits = match clauses.get(clause2_id) {
578 Some(c) if !c.deleted => c.lits.clone(),
579 _ => continue,
580 };
581
582 let common: Vec<Lit> = clause1_lits
584 .iter()
585 .filter(|&lit| clause2_lits.contains(lit))
586 .copied()
587 .collect();
588
589 if common.len() < 2 {
591 continue;
592 }
593
594 let unique1: Vec<Lit> = clause1_lits
596 .iter()
597 .filter(|lit| !common.contains(lit))
598 .copied()
599 .collect();
600
601 let unique2: Vec<Lit> = clause2_lits
602 .iter()
603 .filter(|lit| !common.contains(lit))
604 .copied()
605 .collect();
606
607 let original_size = clause1_lits.len() + clause2_lits.len();
609
610 let new_size = common.len() + unique1.len() + unique2.len() + 3;
613
614 if new_size >= original_size {
616 continue;
617 }
618
619 let new_var = Var::new((self.num_vars + vars_added) as u32);
621 let new_lit = Lit::pos(new_var);
622
623 let mut new_clause1 = common.clone();
629 new_clause1.push(new_lit);
630
631 let mut new_clause2 = vec![new_lit.negate()];
632 new_clause2.extend(&unique1);
633
634 let mut new_clause3 = vec![new_lit.negate()];
635 new_clause3.extend(&unique2);
636
637 if let Some(c) = clauses.get_mut(clause1_id) {
639 c.deleted = true;
640 }
641 if let Some(c) = clauses.get_mut(clause2_id) {
642 c.deleted = true;
643 }
644 self.removed_clauses.insert(clause1_id);
645 self.removed_clauses.insert(clause2_id);
646
647 if !new_clause1.is_empty() {
649 clauses.add_original(new_clause1);
650 }
651 if !new_clause2.is_empty() {
652 clauses.add_original(new_clause2);
653 }
654 if !new_clause3.is_empty() {
655 clauses.add_original(new_clause3);
656 }
657
658 vars_added += 1;
659 break; }
661 }
662
663 self.num_vars += vars_added;
665
666 vars_added
667 }
668
669 pub fn preprocess(&mut self, clauses: &mut ClauseDatabase) -> (usize, usize) {
673 let mut total_clauses = 0;
674 let total_vars = 0;
675
676 loop {
678 let mut changed = false;
679
680 let pure_elim = self.pure_literal_elimination(clauses);
682 if pure_elim > 0 {
683 total_clauses += pure_elim;
684 changed = true;
685 }
686
687 let subsumption = self.subsumption_elimination(clauses);
689 if subsumption > 0 {
690 total_clauses += subsumption;
691 changed = true;
692 }
693
694 let bce = self.blocked_clause_elimination(clauses);
696 if bce > 0 {
697 total_clauses += bce;
698 changed = true;
699 }
700
701 if !changed {
702 break;
703 }
704 }
705
706 (total_clauses, total_vars)
707 }
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713
714 #[test]
715 fn test_tautology_detection() {
716 let v0 = Var(0);
717 let l0 = Lit::pos(v0);
718 let l0_neg = Lit::neg(v0);
719
720 assert!(Preprocessor::is_tautology(&[l0, l0_neg]));
722
723 assert!(!Preprocessor::is_tautology(&[l0, l0]));
725 }
726
727 #[test]
728 fn test_pure_literal() {
729 let mut clauses = ClauseDatabase::new();
730 let v0 = Var(0);
731 let v1 = Var(1);
732
733 clauses.add_original([Lit::pos(v0), Lit::pos(v1)]);
736 clauses.add_original([Lit::pos(v0)]);
737
738 let mut prep = Preprocessor::new(2);
739 let eliminated = prep.pure_literal_elimination(&mut clauses);
740
741 assert_eq!(eliminated, 2);
742 }
743
744 #[test]
745 fn test_subsumption() {
746 let mut clauses = ClauseDatabase::new();
747 let v0 = Var(0);
748 let v1 = Var(1);
749
750 clauses.add_original([Lit::pos(v0)]);
753 clauses.add_original([Lit::pos(v0), Lit::pos(v1)]);
754
755 let mut prep = Preprocessor::new(2);
756 let eliminated = prep.subsumption_elimination(&mut clauses);
757
758 assert_eq!(eliminated, 1);
759 }
760}