#[allow(unused_imports)]
use crate::prelude::*;
pub type Lit = i32;
pub type ClauseId = usize;
#[derive(Debug, Clone)]
pub struct RelevancyConfig {
pub enable_coi: bool,
pub max_depth: u32,
pub incremental: bool,
}
impl Default for RelevancyConfig {
fn default() -> Self {
Self {
enable_coi: true,
max_depth: 100,
incremental: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RelevancyStats {
pub coi_computed: u64,
pub relevant_lits: u64,
pub irrelevant_lits: u64,
pub avg_coi_size: f64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImplicationEdge {
pub antecedent: Lit,
pub consequent: Lit,
pub reason: Option<ClauseId>,
}
pub struct RelevancyTracker {
config: RelevancyConfig,
stats: RelevancyStats,
relevant: FxHashSet<Lit>,
edges: Vec<ImplicationEdge>,
decision_level: u32,
stamp: u32,
lit_stamps: Vec<u32>,
}
impl RelevancyTracker {
pub fn new() -> Self {
Self::with_config(RelevancyConfig::default())
}
pub fn with_config(config: RelevancyConfig) -> Self {
Self {
config,
stats: RelevancyStats::default(),
relevant: FxHashSet::default(),
edges: Vec::new(),
decision_level: 0,
stamp: 0,
lit_stamps: Vec::new(),
}
}
pub fn stats(&self) -> &RelevancyStats {
&self.stats
}
pub fn add_edge(&mut self, edge: ImplicationEdge) {
self.edges.push(edge);
}
pub fn compute_coi(&mut self, roots: &[Lit]) -> FxHashSet<Lit> {
if !self.config.enable_coi {
return roots.iter().copied().collect();
}
self.stats.coi_computed += 1;
let mut coi = FxHashSet::default();
let mut frontier: Vec<Lit> = roots.to_vec();
let mut depth = 0;
while !frontier.is_empty() && depth <= self.config.max_depth {
let mut next_frontier = Vec::new();
for &lit in &frontier {
if coi.insert(lit) {
for edge in &self.edges {
if edge.consequent == lit {
next_frontier.push(edge.antecedent);
}
}
}
}
frontier = next_frontier;
depth += 1;
}
self.stats.relevant_lits += coi.len() as u64;
let total = self.stats.coi_computed;
let prev_avg = self.stats.avg_coi_size;
self.stats.avg_coi_size = (prev_avg * (total - 1) as f64 + coi.len() as f64) / total as f64;
coi
}
pub fn mark_relevant(&mut self, lit: Lit) {
self.stamp += 1;
let var = lit.unsigned_abs() as usize;
if var >= self.lit_stamps.len() {
self.lit_stamps.resize(var + 1, 0);
}
self.lit_stamps[var] = self.stamp;
self.relevant.insert(lit);
self.stats.relevant_lits += 1;
}
pub fn is_relevant(&self, lit: Lit) -> bool {
self.relevant.contains(&lit)
}
pub fn filter_relevant(&self, lits: &[Lit]) -> Vec<Lit> {
lits.iter()
.copied()
.filter(|&lit| self.is_relevant(lit))
.collect()
}
pub fn minimize_clause(&mut self, clause: &[Lit]) -> Vec<Lit> {
if clause.is_empty() {
return Vec::new();
}
let coi = self.compute_coi(&clause[0..1]);
let minimized: Vec<Lit> = clause
.iter()
.copied()
.filter(|&lit| coi.contains(&lit))
.collect();
self.stats.irrelevant_lits += (clause.len() - minimized.len()) as u64;
minimized
}
pub fn clear(&mut self) {
self.relevant.clear();
if !self.config.incremental {
self.edges.clear();
}
}
pub fn backtrack(&mut self, level: u32) {
self.decision_level = level;
if !self.config.incremental {
self.clear();
}
}
pub fn decision_level(&self) -> u32 {
self.decision_level
}
pub fn num_relevant(&self) -> usize {
self.relevant.len()
}
pub fn num_edges(&self) -> usize {
self.edges.len()
}
}
impl Default for RelevancyTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tracker_creation() {
let tracker = RelevancyTracker::new();
assert_eq!(tracker.num_relevant(), 0);
assert_eq!(tracker.num_edges(), 0);
}
#[test]
fn test_mark_relevant() {
let mut tracker = RelevancyTracker::new();
tracker.mark_relevant(1);
tracker.mark_relevant(-2);
assert!(tracker.is_relevant(1));
assert!(tracker.is_relevant(-2));
assert!(!tracker.is_relevant(3));
}
#[test]
fn test_filter_relevant() {
let mut tracker = RelevancyTracker::new();
tracker.mark_relevant(1);
tracker.mark_relevant(3);
let lits = vec![1, 2, 3, 4];
let filtered = tracker.filter_relevant(&lits);
assert_eq!(filtered, vec![1, 3]);
}
#[test]
fn test_coi_simple() {
let mut tracker = RelevancyTracker::new();
tracker.add_edge(ImplicationEdge {
antecedent: 1,
consequent: 2,
reason: None,
});
tracker.add_edge(ImplicationEdge {
antecedent: 2,
consequent: 3,
reason: None,
});
let coi = tracker.compute_coi(&[3]);
assert!(coi.contains(&3));
assert!(coi.contains(&2));
assert!(coi.contains(&1));
}
#[test]
fn test_minimize_clause() {
let mut tracker = RelevancyTracker::new();
tracker.add_edge(ImplicationEdge {
antecedent: 1,
consequent: 2,
reason: None,
});
let clause = vec![2, 1, 3];
let minimized = tracker.minimize_clause(&clause);
assert!(minimized.contains(&2));
assert!(minimized.contains(&1));
assert!(!minimized.contains(&3));
}
#[test]
fn test_backtrack() {
let mut tracker = RelevancyTracker::new();
tracker.mark_relevant(1);
tracker.mark_relevant(2);
tracker.backtrack(0);
assert!(tracker.is_relevant(1));
}
#[test]
fn test_coi_depth_limit() {
let config = RelevancyConfig {
enable_coi: true,
max_depth: 2,
incremental: true,
};
let mut tracker = RelevancyTracker::with_config(config);
for i in 1..5 {
tracker.add_edge(ImplicationEdge {
antecedent: i,
consequent: i + 1,
reason: None,
});
}
let coi = tracker.compute_coi(&[5]);
assert!(coi.contains(&5));
assert!(coi.contains(&4));
assert!(coi.contains(&3));
}
#[test]
fn test_stats() {
let mut tracker = RelevancyTracker::new();
tracker.mark_relevant(1);
tracker.compute_coi(&[1]);
let stats = tracker.stats();
assert_eq!(stats.coi_computed, 1);
assert!(stats.relevant_lits > 0);
}
}