#![allow(missing_docs)] #[allow(unused_imports)]
use crate::prelude::*;
pub type Lit = i32;
pub type Var = u32;
pub type ClauseId = usize;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Clause {
pub literals: Vec<Lit>,
}
impl Clause {
pub fn new(literals: Vec<Lit>) -> Self {
Self { literals }
}
pub fn is_unit(&self) -> bool {
self.literals.len() == 1
}
pub fn is_binary(&self) -> bool {
self.literals.len() == 2
}
pub fn is_empty(&self) -> bool {
self.literals.is_empty()
}
pub fn contains(&self, lit: Lit) -> bool {
self.literals.contains(&lit)
}
pub fn size(&self) -> usize {
self.literals.len()
}
}
#[derive(Debug, Clone, Default)]
pub struct PreprocessingStats {
pub variables_eliminated: u64,
pub clauses_eliminated: u64,
pub literals_eliminated: u64,
pub subsumptions: u64,
pub self_subsuming_resolutions: u64,
pub vivifications: u64,
pub blocked_clauses: u64,
pub equivalent_literals: u64,
}
#[derive(Debug, Clone)]
pub struct PreprocessingConfig {
pub enable_bve: bool,
pub bve_clause_limit: usize,
pub enable_subsumption: bool,
pub enable_vivification: bool,
pub enable_bce: bool,
pub enable_equiv_literals: bool,
pub max_iterations: usize,
}
impl Default for PreprocessingConfig {
fn default() -> Self {
Self {
enable_bve: true,
bve_clause_limit: 100,
enable_subsumption: true,
enable_vivification: true,
enable_bce: true,
enable_equiv_literals: true,
max_iterations: 10,
}
}
}
pub struct AdvancedPreprocessor {
config: PreprocessingConfig,
stats: PreprocessingStats,
clauses: Vec<Clause>,
occurrences: FxHashMap<Lit, Vec<ClauseId>>,
elim_order: Vec<Var>,
eliminated: FxHashSet<Var>,
}
impl AdvancedPreprocessor {
pub fn new(config: PreprocessingConfig) -> Self {
Self {
config,
stats: PreprocessingStats::default(),
clauses: Vec::new(),
occurrences: FxHashMap::default(),
elim_order: Vec::new(),
eliminated: FxHashSet::default(),
}
}
pub fn preprocess(&mut self, clauses: Vec<Clause>) -> Result<Vec<Clause>, String> {
self.clauses = clauses;
self.build_occurrence_lists();
for _iteration in 0..self.config.max_iterations {
let mut changed = false;
changed |= self.unit_propagation()?;
if self.config.enable_subsumption {
changed |= self.subsumption()?;
}
if self.config.enable_subsumption {
changed |= self.self_subsuming_resolution()?;
}
if self.config.enable_vivification {
changed |= self.vivification()?;
}
if self.config.enable_bve {
changed |= self.bounded_variable_elimination()?;
}
if self.config.enable_bce {
changed |= self.blocked_clause_elimination()?;
}
if self.config.enable_equiv_literals {
changed |= self.equivalent_literal_substitution()?;
}
if !changed {
break;
}
}
Ok(self.clauses.clone())
}
fn build_occurrence_lists(&mut self) {
self.occurrences.clear();
for (clause_id, clause) in self.clauses.iter().enumerate() {
for &lit in &clause.literals {
self.occurrences.entry(lit).or_default().push(clause_id);
}
}
}
fn unit_propagation(&mut self) -> Result<bool, String> {
let mut changed = false;
loop {
let unit_clauses: Vec<_> = self
.clauses
.iter()
.filter(|c| c.is_unit())
.cloned()
.collect();
if unit_clauses.is_empty() {
break;
}
for unit_clause in unit_clauses {
let unit_lit = unit_clause.literals[0];
self.propagate_literal(unit_lit)?;
changed = true;
}
}
Ok(changed)
}
fn propagate_literal(&mut self, lit: Lit) -> Result<(), String> {
let neg_lit = -lit;
self.clauses.retain(|c| !c.contains(lit));
for clause in &mut self.clauses {
if clause.contains(neg_lit) {
clause.literals.retain(|&l| l != neg_lit);
self.stats.literals_eliminated += 1;
}
}
if self.clauses.iter().any(|c| c.is_empty()) {
return Err("Formula is unsatisfiable".to_string());
}
self.build_occurrence_lists();
Ok(())
}
fn subsumption(&mut self) -> Result<bool, String> {
let mut changed = false;
let mut to_remove = FxHashSet::default();
for i in 0..self.clauses.len() {
if to_remove.contains(&i) {
continue;
}
for j in (i + 1)..self.clauses.len() {
if to_remove.contains(&j) {
continue;
}
if self.subsumes(&self.clauses[i], &self.clauses[j]) {
to_remove.insert(j);
self.stats.subsumptions += 1;
changed = true;
}
else if self.subsumes(&self.clauses[j], &self.clauses[i]) {
to_remove.insert(i);
self.stats.subsumptions += 1;
changed = true;
break;
}
}
}
let mut new_clauses = Vec::new();
for (i, clause) in self.clauses.iter().enumerate() {
if !to_remove.contains(&i) {
new_clauses.push(clause.clone());
}
}
self.clauses = new_clauses;
self.stats.clauses_eliminated += to_remove.len() as u64;
if changed {
self.build_occurrence_lists();
}
Ok(changed)
}
fn subsumes(&self, clause1: &Clause, clause2: &Clause) -> bool {
if clause1.size() > clause2.size() {
return false;
}
clause1.literals.iter().all(|lit| clause2.contains(*lit))
}
fn self_subsuming_resolution(&mut self) -> Result<bool, String> {
let mut changed = false;
for i in 0..self.clauses.len() {
for j in (i + 1)..self.clauses.len() {
if let Some(resolvent) =
self.try_self_subsuming_resolution(&self.clauses[i], &self.clauses[j])
{
if self.clauses[i].size() > self.clauses[j].size() {
self.clauses[i] = resolvent;
} else {
self.clauses[j] = resolvent;
}
self.stats.self_subsuming_resolutions += 1;
changed = true;
}
}
}
if changed {
self.build_occurrence_lists();
}
Ok(changed)
}
fn try_self_subsuming_resolution(&self, c1: &Clause, c2: &Clause) -> Option<Clause> {
for &lit in &c1.literals {
if c2.contains(-lit) {
let c1_without_lit: FxHashSet<_> =
c1.literals.iter().filter(|&&l| l != lit).copied().collect();
if c1_without_lit.iter().all(|l| c2.contains(*l)) {
let resolvent_lits: Vec<_> = c2
.literals
.iter()
.filter(|&&l| l != -lit)
.copied()
.collect();
return Some(Clause::new(resolvent_lits));
}
}
}
None
}
fn vivification(&mut self) -> Result<bool, String> {
let mut changed = false;
let indices_to_process: Vec<usize> = self
.clauses
.iter()
.enumerate()
.filter(|(_, clause)| clause.size() > 2)
.map(|(i, _)| i)
.collect();
for idx in indices_to_process {
let clause = &self.clauses[idx];
if let Some(strengthened) = self.try_strengthen_clause(clause) {
self.clauses[idx] = strengthened;
self.stats.vivifications += 1;
changed = true;
}
}
if changed {
self.build_occurrence_lists();
}
Ok(changed)
}
fn try_strengthen_clause(&self, clause: &Clause) -> Option<Clause> {
let mut unique_lits: Vec<_> = clause.literals.clone();
unique_lits.sort();
unique_lits.dedup();
if unique_lits.len() < clause.literals.len() {
Some(Clause::new(unique_lits))
} else {
None
}
}
fn bounded_variable_elimination(&mut self) -> Result<bool, String> {
let mut changed = false;
self.compute_elimination_order();
for &var in &self.elim_order.clone() {
if self.eliminated.contains(&var) {
continue;
}
if self.try_eliminate_variable(var)? {
self.eliminated.insert(var);
self.stats.variables_eliminated += 1;
changed = true;
}
}
if changed {
self.build_occurrence_lists();
}
Ok(changed)
}
fn compute_elimination_order(&mut self) {
let mut var_occurrences: FxHashMap<Var, usize> = FxHashMap::default();
for clause in &self.clauses {
for &lit in &clause.literals {
let var = lit.unsigned_abs();
*var_occurrences.entry(var).or_insert(0) += 1;
}
}
let mut vars: Vec<_> = var_occurrences.into_iter().collect();
vars.sort_by_key(|(_, count)| *count);
self.elim_order = vars.into_iter().map(|(var, _)| var).collect();
}
fn try_eliminate_variable(&mut self, var: Var) -> Result<bool, String> {
let pos_lit = var as Lit;
let neg_lit = -(var as Lit);
let pos_clauses: Vec<_> = self
.clauses
.iter()
.filter(|c| c.contains(pos_lit))
.cloned()
.collect();
let neg_clauses: Vec<_> = self
.clauses
.iter()
.filter(|c| c.contains(neg_lit))
.cloned()
.collect();
let mut resolvents = Vec::new();
for pos_clause in &pos_clauses {
for neg_clause in &neg_clauses {
if let Some(resolvent) = self.resolve(pos_clause, neg_clause, pos_lit) {
resolvents.push(resolvent);
}
}
}
let old_clause_count = pos_clauses.len() + neg_clauses.len();
let new_clause_count = resolvents.len();
if new_clause_count > self.config.bve_clause_limit
|| new_clause_count > old_clause_count * 2
{
return Ok(false);
}
self.clauses
.retain(|c| !c.contains(pos_lit) && !c.contains(neg_lit));
self.clauses.extend(resolvents);
self.stats.clauses_eliminated += old_clause_count as u64;
Ok(true)
}
fn resolve(&self, c1: &Clause, c2: &Clause, pivot: Lit) -> Option<Clause> {
let mut resolvent_lits = FxHashSet::default();
for &lit in &c1.literals {
if lit != pivot {
resolvent_lits.insert(lit);
}
}
for &lit in &c2.literals {
if lit != -pivot {
if resolvent_lits.contains(&-lit) {
return None;
}
resolvent_lits.insert(lit);
}
}
Some(Clause::new(resolvent_lits.into_iter().collect()))
}
fn blocked_clause_elimination(&mut self) -> Result<bool, String> {
let mut changed = false;
let mut to_remove = FxHashSet::default();
for (clause_id, clause) in self.clauses.iter().enumerate() {
for &lit in &clause.literals {
if self.is_blocked(clause, lit) {
to_remove.insert(clause_id);
self.stats.blocked_clauses += 1;
changed = true;
break;
}
}
}
let mut new_clauses = Vec::new();
for (i, clause) in self.clauses.iter().enumerate() {
if !to_remove.contains(&i) {
new_clauses.push(clause.clone());
}
}
self.clauses = new_clauses;
if changed {
self.build_occurrence_lists();
}
Ok(changed)
}
fn is_blocked(&self, clause: &Clause, lit: Lit) -> bool {
let neg_lit = -lit;
for other_clause in &self.clauses {
if other_clause.contains(neg_lit)
&& let Some(_resolvent) = self.resolve(clause, other_clause, lit)
{
return false;
}
}
true
}
fn equivalent_literal_substitution(&mut self) -> Result<bool, String> {
let mut changed = false;
let equiv_classes = self.find_equivalent_literals();
for (representative, equivalents) in equiv_classes {
for equiv in equivalents {
self.substitute_literal(equiv, representative);
self.stats.equivalent_literals += 1;
changed = true;
}
}
if changed {
self.build_occurrence_lists();
}
Ok(changed)
}
fn find_equivalent_literals(&self) -> FxHashMap<Lit, Vec<Lit>> {
let mut implications: FxHashMap<Lit, Vec<Lit>> = FxHashMap::default();
for clause in &self.clauses {
if clause.is_binary() {
let lit1 = clause.literals[0];
let lit2 = clause.literals[1];
implications.entry(-lit1).or_default().push(lit2);
implications.entry(-lit2).or_default().push(lit1);
}
}
let mut equiv_classes = FxHashMap::default();
for (&lit1, targets) in &implications {
for &lit2 in targets {
if implications.get(&lit2).is_some_and(|t| t.contains(&lit1)) {
equiv_classes
.entry(lit1.min(lit2))
.or_insert_with(Vec::new)
.push(lit1.max(lit2));
}
}
}
equiv_classes
}
fn substitute_literal(&mut self, from: Lit, to: Lit) {
for clause in &mut self.clauses {
for lit in &mut clause.literals {
if *lit == from {
*lit = to;
} else if *lit == -from {
*lit = -to;
}
}
}
}
pub fn stats(&self) -> &PreprocessingStats {
&self.stats
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_preprocessor_creation() {
let config = PreprocessingConfig::default();
let preprocessor = AdvancedPreprocessor::new(config);
assert_eq!(preprocessor.stats.variables_eliminated, 0);
}
#[test]
fn test_unit_propagation() {
let config = PreprocessingConfig::default();
let mut preprocessor = AdvancedPreprocessor::new(config);
let clauses = vec![
Clause::new(vec![1]), Clause::new(vec![1, 2]), Clause::new(vec![-1, 3]), ];
let result = preprocessor.preprocess(clauses);
assert!(result.is_ok());
let preprocessed = result.expect("Preprocessing must succeed");
assert!(preprocessed.len() < 3);
}
#[test]
fn test_subsumption() {
let config = PreprocessingConfig::default();
let mut preprocessor = AdvancedPreprocessor::new(config);
let clauses = vec![
Clause::new(vec![1, 2]), Clause::new(vec![1, 2, 3]), ];
let result = preprocessor.preprocess(clauses);
assert!(result.is_ok());
let preprocessed = result.expect("Preprocessing must succeed");
assert!(preprocessed.len() <= 2);
}
#[test]
fn test_clause_operations() {
let clause = Clause::new(vec![1, 2, 3]);
assert!(clause.contains(2));
assert!(!clause.contains(4));
assert_eq!(clause.size(), 3);
assert!(!clause.is_unit());
assert!(!clause.is_binary());
}
#[test]
fn test_subsumes_check() {
let preprocessor = AdvancedPreprocessor::new(PreprocessingConfig::default());
let c1 = Clause::new(vec![1, 2]);
let c2 = Clause::new(vec![1, 2, 3]);
assert!(preprocessor.subsumes(&c1, &c2));
assert!(!preprocessor.subsumes(&c2, &c1));
}
#[test]
fn test_resolution() {
let preprocessor = AdvancedPreprocessor::new(PreprocessingConfig::default());
let c1 = Clause::new(vec![1, 2]);
let c2 = Clause::new(vec![-1, 3]);
let resolvent = preprocessor.resolve(&c1, &c2, 1);
assert!(resolvent.is_some());
let res = resolvent.expect("Resolution must produce resolvent");
assert!(res.contains(2));
assert!(res.contains(3));
assert!(!res.contains(1));
}
#[test]
fn test_tautology_detection() {
let preprocessor = AdvancedPreprocessor::new(PreprocessingConfig::default());
let c1 = Clause::new(vec![1, 2]);
let c2 = Clause::new(vec![-1, -2]);
let resolvent = preprocessor.resolve(&c1, &c2, 1);
assert!(resolvent.is_none());
}
#[test]
fn test_empty_clause() {
let clause = Clause::new(vec![]);
assert!(clause.is_empty());
}
}