#![allow(missing_docs, clippy::ptr_arg)]
#[allow(unused_imports)]
use crate::prelude::*;
pub type Lit = i32;
pub type ClauseId = usize;
pub type Level = usize;
#[derive(Debug, Clone)]
pub enum Reason {
Decision,
Clause(ClauseId),
Binary(Lit),
Theory(Vec<Lit>),
}
#[derive(Debug, Clone)]
pub struct LitInfo {
pub level: Level,
pub reason: Reason,
pub seen: bool,
pub poisoned: bool,
}
#[derive(Debug, Clone, Default)]
pub struct RecursiveMinStats {
pub clauses_minimized: u64,
pub literals_removed: u64,
pub recursive_calls: u64,
pub binary_minimizations: u64,
pub stamp_minimizations: u64,
pub self_subsuming_resolutions: u64,
}
#[derive(Debug, Clone)]
pub struct RecursiveMinConfig {
pub enable_recursive: bool,
pub enable_binary: bool,
pub enable_stamp: bool,
pub max_recursion_depth: usize,
pub enable_self_subsumption: bool,
}
impl Default for RecursiveMinConfig {
fn default() -> Self {
Self {
enable_recursive: true,
enable_binary: true,
enable_stamp: true,
max_recursion_depth: 10,
enable_self_subsumption: true,
}
}
}
pub struct RecursiveMinimizer {
config: RecursiveMinConfig,
stats: RecursiveMinStats,
lit_info: FxHashMap<Lit, LitInfo>,
conflict_level: Level,
min_stack: Vec<Lit>,
analyzed: FxHashSet<Lit>,
}
impl RecursiveMinimizer {
pub fn new(config: RecursiveMinConfig) -> Self {
Self {
config,
stats: RecursiveMinStats::default(),
lit_info: FxHashMap::default(),
conflict_level: 0,
min_stack: Vec::new(),
analyzed: FxHashSet::default(),
}
}
pub fn minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
if clause.len() <= 1 {
return Ok(());
}
self.stats.clauses_minimized += 1;
self.conflict_level = clause
.iter()
.map(|&lit| self.get_level(lit))
.max()
.unwrap_or(0);
for &lit in clause.iter() {
if let Some(info) = self.lit_info.get_mut(&lit) {
info.seen = true;
}
}
if self.config.enable_recursive {
self.recursive_minimize(clause)?;
}
if self.config.enable_binary {
self.binary_minimize(clause)?;
}
if self.config.enable_stamp {
self.stamp_minimize(clause)?;
}
if self.config.enable_self_subsumption {
self.detect_self_subsumption(clause)?;
}
for &lit in clause.iter() {
if let Some(info) = self.lit_info.get_mut(&lit) {
info.seen = false;
}
}
Ok(())
}
fn recursive_minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
let mut to_remove = Vec::new();
for &lit in clause.iter() {
if self.get_level(lit) == self.conflict_level && self.is_decision(lit) {
continue;
}
self.analyzed.clear();
if self.can_remove_recursive(lit, 0)? {
to_remove.push(lit);
self.stats.literals_removed += 1;
}
}
clause.retain(|lit| !to_remove.contains(lit));
Ok(())
}
fn can_remove_recursive(&mut self, lit: Lit, depth: usize) -> Result<bool, String> {
if depth > self.config.max_recursion_depth {
return Ok(false);
}
self.stats.recursive_calls += 1;
if self.analyzed.contains(&lit) {
return Ok(false);
}
self.analyzed.insert(lit);
let reason = match self.lit_info.get(&lit) {
Some(info) => info.reason.clone(),
None => return Ok(false),
};
match reason {
Reason::Decision => Ok(false),
Reason::Binary(other_lit) => {
self.is_redundant(other_lit, depth)
}
Reason::Clause(clause_id) => {
let reason_lits = self.get_clause_literals(clause_id)?;
for &reason_lit in &reason_lits {
if reason_lit == lit {
continue;
}
if !self.is_redundant(reason_lit, depth)? {
return Ok(false);
}
}
Ok(true)
}
Reason::Theory(theory_lits) => {
for &theory_lit in &theory_lits {
if theory_lit == lit {
continue;
}
if !self.is_redundant(theory_lit, depth)? {
return Ok(false);
}
}
Ok(true)
}
}
}
fn is_redundant(&mut self, lit: Lit, depth: usize) -> Result<bool, String> {
if self.lit_info.get(&lit).is_some_and(|info| info.seen) {
return Ok(true);
}
if self.get_level(lit) < self.conflict_level {
return Ok(true);
}
self.can_remove_recursive(lit, depth + 1)
}
fn binary_minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
self.stats.binary_minimizations += 1;
let mut to_remove = Vec::new();
for &lit in clause.iter() {
let reason = match self.lit_info.get(&lit) {
Some(info) => &info.reason,
None => continue,
};
if let Reason::Binary(other_lit) = reason {
if clause.contains(&-other_lit) {
to_remove.push(lit);
self.stats.literals_removed += 1;
}
}
}
clause.retain(|lit| !to_remove.contains(lit));
Ok(())
}
fn stamp_minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
self.stats.stamp_minimizations += 1;
let mut abstract_level = FxHashSet::default();
for &lit in clause.iter() {
if self.get_level(lit) == self.conflict_level {
abstract_level.insert(lit);
}
}
let mut to_remove = Vec::new();
for &lit in clause.iter() {
if self.get_level(lit) < self.conflict_level {
continue;
}
if self.can_stamp_out(lit, &abstract_level)? {
to_remove.push(lit);
self.stats.literals_removed += 1;
}
}
clause.retain(|lit| !to_remove.contains(lit));
Ok(())
}
fn can_stamp_out(&self, lit: Lit, abstract_level: &FxHashSet<Lit>) -> Result<bool, String> {
let reason = match self.lit_info.get(&lit) {
Some(info) => &info.reason,
None => return Ok(false),
};
match reason {
Reason::Decision => Ok(false),
Reason::Binary(other_lit) => Ok(abstract_level.contains(other_lit)),
Reason::Clause(clause_id) => {
let reason_lits = self.get_clause_literals(*clause_id)?;
Ok(reason_lits
.iter()
.filter(|&&l| l != lit)
.all(|l| abstract_level.contains(l)))
}
Reason::Theory(theory_lits) => Ok(theory_lits
.iter()
.filter(|&&l| l != lit)
.all(|l| abstract_level.contains(l))),
}
}
fn detect_self_subsumption(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
for &lit in clause.clone().iter() {
let reason = match self.lit_info.get(&lit) {
Some(info) => &info.reason,
None => continue,
};
if let Reason::Clause(clause_id) = reason
&& self.is_self_subsuming(clause, lit, *clause_id)?
{
self.stats.self_subsuming_resolutions += 1;
}
}
Ok(())
}
fn is_self_subsuming(
&self,
clause: &[Lit],
lit: Lit,
reason_clause_id: ClauseId,
) -> Result<bool, String> {
let reason_lits = self.get_clause_literals(reason_clause_id)?;
let mut resolvent: FxHashSet<_> = clause.iter().filter(|&&l| l != lit).copied().collect();
for &reason_lit in &reason_lits {
if reason_lit != -lit {
resolvent.insert(reason_lit);
}
}
Ok(resolvent.len() < clause.len())
}
pub fn set_lit_info(&mut self, lit: Lit, level: Level, reason: Reason) {
self.lit_info.insert(
lit,
LitInfo {
level,
reason,
seen: false,
poisoned: false,
},
);
}
fn get_level(&self, lit: Lit) -> Level {
self.lit_info.get(&lit).map_or(0, |info| info.level)
}
fn is_decision(&self, lit: Lit) -> bool {
self.lit_info
.get(&lit)
.is_some_and(|info| matches!(info.reason, Reason::Decision))
}
fn get_clause_literals(&self, _clause_id: ClauseId) -> Result<Vec<Lit>, String> {
Ok(vec![])
}
pub fn stats(&self) -> &RecursiveMinStats {
&self.stats
}
pub fn reset(&mut self) {
self.lit_info.clear();
self.min_stack.clear();
self.analyzed.clear();
self.conflict_level = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_minimizer_creation() {
let config = RecursiveMinConfig::default();
let minimizer = RecursiveMinimizer::new(config);
assert_eq!(minimizer.stats.clauses_minimized, 0);
}
#[test]
fn test_empty_clause() {
let config = RecursiveMinConfig::default();
let mut minimizer = RecursiveMinimizer::new(config);
let mut clause = vec![];
let result = minimizer.minimize(&mut clause);
assert!(result.is_ok());
assert_eq!(clause.len(), 0);
}
#[test]
fn test_unit_clause() {
let config = RecursiveMinConfig::default();
let mut minimizer = RecursiveMinimizer::new(config);
let mut clause = vec![1];
let result = minimizer.minimize(&mut clause);
assert!(result.is_ok());
assert_eq!(clause.len(), 1);
}
#[test]
fn test_set_lit_info() {
let config = RecursiveMinConfig::default();
let mut minimizer = RecursiveMinimizer::new(config);
minimizer.set_lit_info(1, 5, Reason::Decision);
assert_eq!(minimizer.get_level(1), 5);
assert!(minimizer.is_decision(1));
}
#[test]
fn test_binary_reason() {
let config = RecursiveMinConfig::default();
let mut minimizer = RecursiveMinimizer::new(config);
minimizer.set_lit_info(1, 3, Reason::Binary(2));
assert_eq!(minimizer.get_level(1), 3);
assert!(!minimizer.is_decision(1));
}
#[test]
fn test_theory_reason() {
let config = RecursiveMinConfig::default();
let mut minimizer = RecursiveMinimizer::new(config);
minimizer.set_lit_info(1, 4, Reason::Theory(vec![2, 3, 4]));
assert_eq!(minimizer.get_level(1), 4);
}
#[test]
fn test_conflict_level_computation() {
let config = RecursiveMinConfig::default();
let mut minimizer = RecursiveMinimizer::new(config);
minimizer.set_lit_info(1, 3, Reason::Decision);
minimizer.set_lit_info(2, 5, Reason::Decision);
minimizer.set_lit_info(3, 4, Reason::Decision);
let mut clause = vec![1, 2, 3];
let _ = minimizer.minimize(&mut clause);
assert_eq!(minimizer.conflict_level, 5);
}
#[test]
fn test_recursive_minimization_disabled() {
let config = RecursiveMinConfig {
enable_recursive: false,
..Default::default()
};
let mut minimizer = RecursiveMinimizer::new(config);
minimizer.set_lit_info(1, 3, Reason::Decision);
minimizer.set_lit_info(2, 3, Reason::Binary(1));
let mut clause = vec![1, 2];
let _ = minimizer.minimize(&mut clause);
assert!(clause.contains(&1) || clause.contains(&2));
}
#[test]
fn test_stats_tracking() {
let config = RecursiveMinConfig::default();
let mut minimizer = RecursiveMinimizer::new(config);
minimizer.set_lit_info(1, 3, Reason::Decision);
minimizer.set_lit_info(2, 3, Reason::Decision);
let mut clause = vec![1, 2];
let _ = minimizer.minimize(&mut clause);
assert_eq!(minimizer.stats.clauses_minimized, 1);
}
#[test]
fn test_reset() {
let config = RecursiveMinConfig::default();
let mut minimizer = RecursiveMinimizer::new(config);
minimizer.set_lit_info(1, 3, Reason::Decision);
minimizer.conflict_level = 5;
minimizer.reset();
assert!(minimizer.lit_info.is_empty());
assert_eq!(minimizer.conflict_level, 0);
}
#[test]
fn test_max_recursion_depth() {
let config = RecursiveMinConfig {
max_recursion_depth: 2,
..Default::default()
};
let mut minimizer = RecursiveMinimizer::new(config);
minimizer.set_lit_info(1, 5, Reason::Binary(2));
minimizer.set_lit_info(2, 5, Reason::Binary(3));
minimizer.set_lit_info(3, 5, Reason::Binary(4));
minimizer.set_lit_info(4, 5, Reason::Binary(5));
minimizer.set_lit_info(5, 5, Reason::Decision);
let result = minimizer.can_remove_recursive(1, 0);
assert!(result.is_ok());
}
}