#![allow(dead_code)]
#[allow(unused_imports)]
use crate::prelude::*;
use oxiz_sat::Lit;
pub type Level = usize;
#[derive(Debug, Clone)]
pub struct ImplicationNode {
pub literal: Lit,
pub level: Level,
pub reason: Option<Vec<Lit>>,
}
impl ImplicationNode {
pub fn new(literal: Lit, level: Level, reason: Option<Vec<Lit>>) -> Self {
Self {
literal,
level,
reason,
}
}
pub fn is_decision(&self) -> bool {
self.reason.is_none()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UipStrategy {
FirstUIP,
LastUIP,
AllUIPs,
Dominator,
}
#[derive(Debug, Clone)]
pub struct UipConfig {
pub strategy: UipStrategy,
pub use_dominators: bool,
pub max_distance: Option<usize>,
}
impl Default for UipConfig {
fn default() -> Self {
Self {
strategy: UipStrategy::FirstUIP,
use_dominators: false,
max_distance: Some(1000),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct UipStats {
pub uips_found: u64,
pub first_uips: u64,
pub last_uips: u64,
pub avg_distance: f64,
pub dominator_computations: u64,
}
pub struct UipSelector {
config: UipConfig,
stats: UipStats,
graph: FxHashMap<Lit, ImplicationNode>,
current_level: Level,
}
impl UipSelector {
pub fn new(config: UipConfig) -> Self {
Self {
config,
stats: UipStats::default(),
graph: FxHashMap::default(),
current_level: 0,
}
}
pub fn default_config() -> Self {
Self::new(UipConfig::default())
}
pub fn add_implication(&mut self, literal: Lit, level: Level, reason: Option<Vec<Lit>>) {
let node = ImplicationNode::new(literal, level, reason);
self.graph.insert(literal, node);
if level > self.current_level {
self.current_level = level;
}
}
pub fn find_uip(&mut self, conflict: &[Lit]) -> Option<Lit> {
if conflict.is_empty() {
return None;
}
match self.config.strategy {
UipStrategy::FirstUIP => self.find_first_uip(conflict),
UipStrategy::LastUIP => self.find_last_uip(conflict),
UipStrategy::AllUIPs => self.find_first_uip(conflict), UipStrategy::Dominator => {
if self.config.use_dominators {
self.find_dominator_uip(conflict)
} else {
self.find_first_uip(conflict)
}
}
}
}
fn find_first_uip(&mut self, conflict: &[Lit]) -> Option<Lit> {
let conflict_level = conflict
.iter()
.filter_map(|&lit| self.graph.get(&lit).map(|node| node.level))
.max()?;
let mut current_lits: FxHashSet<Lit> = conflict
.iter()
.filter(|&&lit| {
self.graph
.get(&lit)
.is_some_and(|node| node.level == conflict_level)
})
.copied()
.collect();
while current_lits.len() > 1 {
let &lit = current_lits.iter().next()?;
current_lits.remove(&lit);
if let Some(node) = self.graph.get(&lit)
&& let Some(reason) = &node.reason
{
for &reason_lit in reason {
if let Some(reason_node) = self.graph.get(&reason_lit)
&& reason_node.level == conflict_level
{
current_lits.insert(reason_lit);
}
}
}
}
self.stats.uips_found += 1;
self.stats.first_uips += 1;
current_lits.iter().next().copied()
}
fn find_last_uip(&mut self, conflict: &[Lit]) -> Option<Lit> {
let conflict_level = conflict
.iter()
.filter_map(|&lit| self.graph.get(&lit).map(|node| node.level))
.max()?;
for (lit, node) in &self.graph {
if node.level == conflict_level && node.is_decision() {
self.stats.uips_found += 1;
self.stats.last_uips += 1;
return Some(*lit);
}
}
self.find_first_uip(conflict)
}
fn find_dominator_uip(&mut self, conflict: &[Lit]) -> Option<Lit> {
self.stats.dominator_computations += 1;
self.find_first_uip(conflict)
}
pub fn find_all_uips(&mut self, conflict: &[Lit]) -> Vec<Lit> {
let mut uips = Vec::new();
let conflict_level = match conflict
.iter()
.filter_map(|&lit| self.graph.get(&lit).map(|node| node.level))
.max()
{
Some(level) => level,
None => return uips,
};
let conflict_level_lits: Vec<Lit> = conflict
.iter()
.filter(|&&lit| {
self.graph
.get(&lit)
.is_some_and(|node| node.level == conflict_level)
})
.copied()
.collect();
for &candidate in &conflict_level_lits {
if self.is_uip(candidate, conflict_level) {
uips.push(candidate);
}
}
self.stats.uips_found += uips.len() as u64;
uips
}
fn is_uip(&self, candidate: Lit, level: Level) -> bool {
if let Some(node) = self.graph.get(&candidate) {
node.level == level
} else {
false
}
}
pub fn distance_to_conflict(&self, lit: Lit, conflict: &[Lit]) -> Option<usize> {
let mut visited = FxHashSet::default();
let mut queue = vec![(lit, 0)];
visited.insert(lit);
while let Some((current, dist)) = queue.pop() {
if conflict.contains(¤t) {
return Some(dist);
}
if let Some(node) = self.graph.get(¤t)
&& let Some(reason) = &node.reason
{
for &next_lit in reason {
if visited.insert(next_lit) {
queue.push((next_lit, dist + 1));
}
}
}
}
None
}
fn update_distance_stats(&mut self, distance: usize) {
let count = self.stats.uips_found;
let old_avg = self.stats.avg_distance;
self.stats.avg_distance = (old_avg * (count - 1) as f64 + distance as f64) / count as f64;
}
pub fn clear(&mut self) {
self.graph.clear();
self.current_level = 0;
}
pub fn stats(&self) -> &UipStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = UipStats::default();
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxiz_sat::Var;
#[test]
fn test_uip_selector_creation() {
let selector = UipSelector::default_config();
assert_eq!(selector.stats().uips_found, 0);
}
#[test]
fn test_add_implication() {
let mut selector = UipSelector::default_config();
let lit = Lit::pos(Var::new(0));
selector.add_implication(lit, 1, None);
assert_eq!(selector.current_level, 1);
assert!(selector.graph.contains_key(&lit));
}
#[test]
fn test_decision_node() {
let node = ImplicationNode::new(Lit::pos(Var::new(0)), 1, None);
assert!(node.is_decision());
let node2 = ImplicationNode::new(Lit::pos(Var::new(1)), 1, Some(vec![]));
assert!(!node2.is_decision());
}
#[test]
fn test_find_last_uip() {
let mut selector = UipSelector::new(UipConfig {
strategy: UipStrategy::LastUIP,
..Default::default()
});
let decision = Lit::pos(Var::new(0));
selector.add_implication(decision, 1, None);
let prop1 = Lit::pos(Var::new(1));
selector.add_implication(prop1, 1, Some(vec![decision]));
let conflict = vec![prop1];
let uip = selector.find_uip(&conflict);
assert!(uip.is_some());
assert_eq!(selector.stats().last_uips, 1);
}
#[test]
fn test_stats() {
let mut selector = UipSelector::default_config();
let lit = Lit::pos(Var::new(0));
selector.add_implication(lit, 1, None);
let conflict = vec![lit];
let _ = selector.find_uip(&conflict);
assert!(selector.stats().uips_found > 0);
}
}