#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
use crate::sat::clause::Clause;
use crate::sat::clause_storage::LiteralStorage;
use crate::sat::literal::Literal;
use rustc_hash::FxHashSet;
use std::fmt::Debug;
use std::sync::Arc;
pub trait Preprocessor<L: Literal, S: LiteralStorage<L>> {
fn preprocess(&self, cnf: &[Clause<L, S>]) -> Vec<Clause<L, S>>;
}
#[derive(Clone, Default)]
pub struct PreprocessorChain<L: Literal, S: LiteralStorage<L>> {
preprocessors: Vec<Arc<dyn Preprocessor<L, S>>>,
}
impl<L: Literal, S: LiteralStorage<L>> Debug for PreprocessorChain<L, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PreprocessorChain")
.field("num_preprocessors", &self.preprocessors.len())
.finish()
}
}
impl<L: Literal, S: LiteralStorage<L>> PreprocessorChain<L, S> {
#[must_use]
#[allow(dead_code)]
pub const fn new() -> Self {
Self {
preprocessors: Vec::new(),
}
}
#[must_use]
#[allow(dead_code)]
pub fn add_preprocessor<P: Preprocessor<L, S> + 'static>(mut self, preprocessor: P) -> Self {
let arc_preprocessor = Arc::new(preprocessor);
self.preprocessors.push(arc_preprocessor);
self
}
}
impl<L: Literal, S: LiteralStorage<L>> Preprocessor<L, S> for PreprocessorChain<L, S> {
fn preprocess(&self, cnf: &[Clause<L, S>]) -> Vec<Clause<L, S>> {
self.preprocessors
.iter()
.fold(Vec::from(cnf), |current_cnf, preprocessor_arc| {
preprocessor_arc.preprocess(¤t_cnf)
})
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct PureLiteralElimination;
impl PureLiteralElimination {
pub fn find_pures<L: Literal, S: LiteralStorage<L>>(cnf: &[Clause<L, S>]) -> FxHashSet<L> {
let mut pures = FxHashSet::default();
let mut impures = FxHashSet::default();
for clause in cnf {
for &lit in clause.iter() {
let var = lit.variable();
if impures.contains(&var) {
continue;
}
if pures.contains(&lit.negated()) {
pures.remove(&lit.negated());
impures.insert(var);
pures.remove(&lit);
} else if !impures.contains(&var) {
pures.insert(lit);
}
}
}
pures
}
}
impl<L: Literal, S: LiteralStorage<L>> Preprocessor<L, S> for PureLiteralElimination {
fn preprocess(&self, cnf: &[Clause<L, S>]) -> Vec<Clause<L, S>> {
let mut current_cnf = cnf.to_vec();
let pures = Self::find_pures(current_cnf.as_slice());
if pures.is_empty() {
return current_cnf;
}
current_cnf.retain(|clause| !clause.iter().any(|lit| pures.contains(lit)));
current_cnf
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct SubsumptionElimination;
impl SubsumptionElimination {
fn is_subset_of<L: Literal, S: LiteralStorage<L>>(
shorter_clause: &Clause<L, S>,
longer_clause: &Clause<L, S>,
) -> bool {
if shorter_clause.len() > longer_clause.len() {
return false; }
if shorter_clause.is_empty() {
return true; }
let mut shorter_iter = shorter_clause.iter();
let mut longer_iter = longer_clause.iter();
let mut current_shorter = shorter_iter.next();
let mut current_longer = longer_iter.next();
while let Some(s_lit) = current_shorter {
while let Some(l_lit) = current_longer {
if l_lit < s_lit {
current_longer = longer_iter.next();
} else {
break;
}
}
match current_longer {
Some(l_lit) if l_lit == s_lit => {
current_shorter = shorter_iter.next();
current_longer = longer_iter.next();
}
_ => {
return false;
}
}
}
true
}
}
impl<L: Literal, S: LiteralStorage<L>> Preprocessor<L, S> for SubsumptionElimination {
fn preprocess(&self, cnf: &[Clause<L, S>]) -> Vec<Clause<L, S>> {
let result = cnf.to_vec();
if result.len() < 2 {
return result;
}
let mut sorted_indices: Vec<usize> = (0..result.len()).collect();
sorted_indices.sort_by_key(|&i| result[i].len());
let mut removed_flags = vec![false; result.len()];
let mut num_removed = 0;
for i in 0..sorted_indices.len() {
let idx_i = sorted_indices[i];
if removed_flags[idx_i] {
continue;
}
for &idx_j in sorted_indices.iter().skip(i + 1) {
if removed_flags[idx_j] {
continue;
}
if Self::is_subset_of(&result[idx_i], &result[idx_j]) {
removed_flags[idx_j] = true;
num_removed += 1;
}
}
}
if num_removed == 0 {
return result;
}
let mut final_clauses = Vec::with_capacity(result.len() - num_removed);
for i in 0..result.len() {
if !removed_flags[i] {
final_clauses.push(result[i].clone());
}
}
final_clauses
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct TautologyElimination;
impl<L: Literal, S: LiteralStorage<L>> Preprocessor<L, S> for TautologyElimination {
fn preprocess(&self, cnf: &[Clause<L, S>]) -> Vec<Clause<L, S>> {
cnf.iter()
.filter(|clause| !clause.is_tautology())
.cloned()
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sat::literal::PackedLiteral;
use smallvec::SmallVec;
type TestLiteral = PackedLiteral;
type TestClauseStorage = SmallVec<[TestLiteral; 8]>;
type TestClause = Clause<TestLiteral, TestClauseStorage>;
fn lit(val: i32) -> TestLiteral {
TestLiteral::from_i32(val)
}
fn clause(lits_dimacs: &[i32]) -> TestClause {
lits_dimacs.iter().map(|&x| lit(x)).collect()
}
fn clause_sorted(lits_dimacs: &[i32]) -> TestClause {
let mut l: Vec<TestLiteral> = lits_dimacs.iter().map(|&x| lit(x)).collect();
l.sort();
Clause::new(&l)
}
#[test]
fn test_tautology_elimination() {
let preprocessor = TautologyElimination;
let cnf = vec![clause(&[1, -1, 2]), clause(&[1, 2]), clause(&[-2, 3, 2])];
let processed_cnf = preprocessor.preprocess(&cnf);
assert_eq!(processed_cnf.len(), 1);
assert_eq!(processed_cnf[0], clause(&[1, 2]));
}
#[test]
fn test_pure_literal_elimination() {
let preprocessor = PureLiteralElimination;
let cnf = vec![clause(&[1, 2]), clause(&[-2, -3]), clause(&[2, -2])];
let processed_cnf = preprocessor.preprocess(&cnf);
assert_eq!(processed_cnf.len(), 1);
assert_eq!(processed_cnf[0], clause(&[2, -2]));
let cnf_no_pures = vec![clause(&[1, 2]), clause(&[-1, -2])];
let processed_no_pures = preprocessor.preprocess(&cnf_no_pures);
assert_eq!(processed_no_pures.len(), 2);
let cnf_iterative = vec![clause(&[1]), clause(&[1, -2]), clause(&[2])];
let processed_iterative_pass1 = preprocessor.preprocess(&cnf_iterative);
assert_eq!(processed_iterative_pass1.len(), 1);
assert_eq!(processed_iterative_pass1[0], clause(&[2]));
let processed_iterative_pass2 = preprocessor.preprocess(&processed_iterative_pass1);
assert_eq!(processed_iterative_pass2.len(), 0);
}
#[test]
fn test_subsumption_elimination() {
let preprocessor = SubsumptionElimination;
let cnf = vec![
clause_sorted(&[1, 2]),
clause_sorted(&[1, 2, 3]),
clause_sorted(&[1, 3]),
clause_sorted(&[1]),
clause_sorted(&[4, 5]),
];
let processed_cnf = preprocessor.preprocess(&cnf);
let expected_cnf = [clause_sorted(&[1]), clause_sorted(&[4, 5])];
let processed_set: FxHashSet<_> = processed_cnf.iter().cloned().collect();
let expected_set: FxHashSet<_> = expected_cnf.iter().cloned().collect();
assert_eq!(processed_set, expected_set);
assert_eq!(processed_cnf.len(), 2);
}
#[test]
fn test_preprocessor_chain() {
let cnf_initial = vec![clause(&[1, -1, 2]), clause(&[1, 2, 3]), clause(&[1, 2])];
let chain = PreprocessorChain::new()
.add_preprocessor(TautologyElimination)
.add_preprocessor(PureLiteralElimination)
.add_preprocessor(SubsumptionElimination);
let processed_cnf = chain.preprocess(&cnf_initial);
assert_eq!(processed_cnf.len(), 0);
}
}