use crate::Clause;
#[allow(unused_imports)]
use crate::prelude::*;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
#[derive(Debug, Clone)]
pub struct SimplificationConfig {
pub enable_subsumption: bool,
pub enable_self_subsuming: bool,
pub enable_duplicate_removal: bool,
pub enable_tautology_removal: bool,
pub chunk_size: usize,
}
impl Default for SimplificationConfig {
fn default() -> Self {
Self {
enable_subsumption: true,
enable_self_subsuming: true,
enable_duplicate_removal: true,
enable_tautology_removal: true,
chunk_size: 1000,
}
}
}
#[derive(Debug, Clone)]
pub struct SimplificationResult {
pub clauses: Vec<Clause>,
pub subsumed_count: usize,
pub tautology_count: usize,
pub duplicate_count: usize,
}
pub struct ParallelClauseSimplifier {
config: SimplificationConfig,
}
impl ParallelClauseSimplifier {
pub fn new(config: SimplificationConfig) -> Self {
Self { config }
}
pub fn default_config() -> Self {
Self::new(SimplificationConfig::default())
}
pub fn simplify(&self, clauses: &[Clause]) -> SimplificationResult {
let mut working_clauses = clauses.to_vec();
let mut stats = SimplificationStats::default();
if self.config.enable_tautology_removal || self.config.enable_duplicate_removal {
let (cleaned, taut_count, dup_count) = self.remove_trivial_clauses(&working_clauses);
working_clauses = cleaned;
stats.tautologies_removed += taut_count;
stats.duplicates_removed += dup_count;
}
if self.config.enable_subsumption {
let (non_subsumed, sub_count) = self.remove_subsumed(&working_clauses);
working_clauses = non_subsumed;
stats.subsumed_removed += sub_count;
}
SimplificationResult {
clauses: working_clauses,
subsumed_count: stats.subsumed_removed,
tautology_count: stats.tautologies_removed,
duplicate_count: stats.duplicates_removed,
}
}
fn remove_trivial_clauses(&self, clauses: &[Clause]) -> (Vec<Clause>, usize, usize) {
let results: Vec<_> = clauses
.par_iter()
.map(|clause| {
let is_taut = self.is_tautology(clause);
(!is_taut, is_taut)
})
.collect();
let mut cleaned: Vec<Clause> = Vec::new();
let mut tautology_count = 0;
for (i, (keep, is_taut)) in results.iter().enumerate() {
if *keep {
cleaned.push(clauses[i].clone());
}
if *is_taut {
tautology_count += 1;
}
}
let before_dedup = cleaned.len();
let mut seen = FxHashSet::default();
let mut deduplicated = Vec::new();
for clause in cleaned {
let mut sorted_lits = clause.lits.iter().copied().collect::<Vec<_>>();
sorted_lits.sort_unstable_by_key(|l| l.code());
if seen.insert(sorted_lits) {
deduplicated.push(clause);
}
}
let duplicate_count = before_dedup - deduplicated.len();
(deduplicated, tautology_count, duplicate_count)
}
fn is_tautology(&self, clause: &Clause) -> bool {
let mut seen = FxHashSet::default();
for &lit in &clause.lits {
let var = lit.var();
if seen.contains(&var) {
let neg_lit = lit.negate();
if clause.lits.contains(&neg_lit) {
return true;
}
}
seen.insert(var);
}
false
}
fn remove_subsumed(&self, clauses: &[Clause]) -> (Vec<Clause>, usize) {
let mut non_subsumed: Vec<Clause> = Vec::new();
let mut subsumed_count = 0;
let subsumption_flags: Vec<_> = clauses
.par_iter()
.enumerate()
.map(|(i, clause)| {
for (j, other) in clauses.iter().enumerate() {
if i != j && self.subsumes(other, clause) {
return true; }
}
false
})
.collect();
for (i, &is_subsumed) in subsumption_flags.iter().enumerate() {
if !is_subsumed {
non_subsumed.push(clauses[i].clone());
} else {
subsumed_count += 1;
}
}
(non_subsumed, subsumed_count)
}
fn subsumes(&self, a: &Clause, b: &Clause) -> bool {
if a.len() > b.len() {
return false;
}
let b_lits: FxHashSet<_> = b.lits.iter().copied().collect();
for &lit in &a.lits {
if !b_lits.contains(&lit) {
return false;
}
}
true
}
pub fn self_subsuming_resolution(&self, clauses: &[Clause]) -> Vec<Clause> {
clauses
.par_iter()
.map(|clause: &Clause| -> Clause {
clause.clone()
})
.collect()
}
}
#[derive(Debug, Clone, Default)]
struct SimplificationStats {
tautologies_removed: usize,
duplicates_removed: usize,
subsumed_removed: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Lit;
fn make_clause(lits: Vec<i32>) -> Clause {
let clause_lits: Vec<Lit> = lits.into_iter().map(Lit::from_dimacs).collect();
Clause::new(clause_lits, false)
}
#[test]
fn test_tautology_detection() {
let simplifier = ParallelClauseSimplifier::default_config();
let taut = make_clause(vec![1, -1, 2]);
assert!(simplifier.is_tautology(&taut));
let non_taut = make_clause(vec![1, 2, 3]);
assert!(!simplifier.is_tautology(&non_taut));
}
#[test]
fn test_subsumption() {
let simplifier = ParallelClauseSimplifier::default_config();
let a = make_clause(vec![1, 2]);
let b = make_clause(vec![1, 2, 3]);
assert!(simplifier.subsumes(&a, &b)); assert!(!simplifier.subsumes(&b, &a)); }
#[test]
fn test_simplification() {
let simplifier = ParallelClauseSimplifier::default_config();
let clauses = vec![
make_clause(vec![1, 2]),
make_clause(vec![1, 2, 3]), make_clause(vec![1, -1]), ];
let result = simplifier.simplify(&clauses);
assert!(result.tautology_count > 0);
assert!(result.subsumed_count > 0);
}
}