#[allow(unused_imports)]
use crate::prelude::*;
use oxiz_sat::Lit;
pub type Var = u32;
pub type Level = u32;
#[derive(Debug, Clone)]
pub struct ImplicationNode {
pub literal: Lit,
pub level: Level,
pub antecedents: Vec<Lit>,
pub consequents: Vec<Lit>,
pub is_decision: bool,
}
impl ImplicationNode {
pub fn decision(literal: Lit, level: Level) -> Self {
Self {
literal,
level,
antecedents: Vec::new(),
consequents: Vec::new(),
is_decision: true,
}
}
pub fn propagated(literal: Lit, level: Level, antecedents: Vec<Lit>) -> Self {
Self {
literal,
level,
antecedents,
consequents: Vec::new(),
is_decision: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ImplicationGraphConfig {
pub enable_compaction: bool,
pub max_size: usize,
}
impl Default for ImplicationGraphConfig {
fn default() -> Self {
Self {
enable_compaction: true,
max_size: 100000,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ImplicationGraphStats {
pub uips_computed: u64,
pub traversals: u64,
pub nodes_added: u64,
pub compactions: u64,
}
pub struct ImplicationGraph {
config: ImplicationGraphConfig,
stats: ImplicationGraphStats,
nodes: FxHashMap<Lit, ImplicationNode>,
current_level: Level,
level_lits: FxHashMap<Level, Vec<Lit>>,
}
impl ImplicationGraph {
pub fn new() -> Self {
Self::with_config(ImplicationGraphConfig::default())
}
pub fn with_config(config: ImplicationGraphConfig) -> Self {
Self {
config,
stats: ImplicationGraphStats::default(),
nodes: FxHashMap::default(),
current_level: 0,
level_lits: FxHashMap::default(),
}
}
pub fn stats(&self) -> &ImplicationGraphStats {
&self.stats
}
pub fn add_decision(&mut self, lit: Lit, level: Level) {
let node = ImplicationNode::decision(lit, level);
self.nodes.insert(lit, node);
self.level_lits.entry(level).or_default().push(lit);
self.current_level = level;
self.stats.nodes_added += 1;
self.check_compaction();
}
pub fn add_propagation(&mut self, lit: Lit, level: Level, antecedents: Vec<Lit>) {
for &ant in &antecedents {
if let Some(ant_node) = self.nodes.get_mut(&ant) {
ant_node.consequents.push(lit);
}
}
let node = ImplicationNode::propagated(lit, level, antecedents);
self.nodes.insert(lit, node);
self.level_lits.entry(level).or_default().push(lit);
self.stats.nodes_added += 1;
self.check_compaction();
}
pub fn find_first_uip(&mut self, conflict_lits: &[Lit], decision_level: Level) -> Option<Lit> {
self.stats.uips_computed += 1;
if conflict_lits.is_empty() {
return None;
}
let conflict_side = self.compute_conflict_side(conflict_lits, decision_level);
if conflict_side.is_empty() {
return conflict_lits.first().copied();
}
let mut uip = None;
let mut max_order = 0;
for &lit in &conflict_side {
if self.is_at_level(lit, decision_level) {
let order = self.get_assignment_order(lit);
if order > max_order {
max_order = order;
uip = Some(lit);
}
}
}
uip
}
fn compute_conflict_side(&mut self, conflict_lits: &[Lit], level: Level) -> FxHashSet<Lit> {
self.stats.traversals += 1;
let mut conflict_side = FxHashSet::default();
let mut frontier: Vec<Lit> = conflict_lits.to_vec();
let mut visited = FxHashSet::default();
while let Some(lit) = frontier.pop() {
if !visited.insert(lit) {
continue;
}
if let Some(node) = self.nodes.get(&lit)
&& node.level == level
{
conflict_side.insert(lit);
for &ant in &node.antecedents {
frontier.push(ant);
}
}
}
conflict_side
}
fn is_at_level(&self, lit: Lit, level: Level) -> bool {
self.nodes
.get(&lit)
.map(|n| n.level == level)
.unwrap_or(false)
}
fn get_assignment_order(&self, lit: Lit) -> usize {
lit.var().0 as usize
}
pub fn get_level(&self, lit: Lit) -> Option<Level> {
self.nodes.get(&lit).map(|n| n.level)
}
pub fn get_antecedents(&self, lit: Lit) -> Option<&[Lit]> {
self.nodes.get(&lit).map(|n| n.antecedents.as_slice())
}
pub fn compute_backjump_level(&self, clause: &[Lit]) -> Level {
if clause.len() <= 1 {
return 0;
}
let mut levels: Vec<Level> = clause
.iter()
.filter_map(|&lit| self.get_level(lit))
.collect();
levels.sort_unstable();
levels.dedup();
if levels.len() >= 2 {
levels[levels.len() - 2]
} else {
0
}
}
pub fn backtrack(&mut self, level: Level) {
self.nodes.retain(|_, node| node.level <= level);
self.level_lits.retain(|&l, _| l <= level);
self.current_level = level;
}
pub fn clear(&mut self) {
self.nodes.clear();
self.level_lits.clear();
self.current_level = 0;
}
fn check_compaction(&mut self) {
if self.config.enable_compaction && self.nodes.len() > self.config.max_size {
self.compact();
}
}
fn compact(&mut self) {
if let Some(&min_level) = self.level_lits.keys().min() {
self.nodes.retain(|_, node| node.level > min_level);
self.level_lits.remove(&min_level);
self.stats.compactions += 1;
}
}
pub fn num_nodes(&self) -> usize {
self.nodes.len()
}
pub fn current_level(&self) -> Level {
self.current_level
}
}
impl Default for ImplicationGraph {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn lit(n: i32) -> Lit {
Lit::from_dimacs(n)
}
#[test]
fn test_graph_creation() {
let graph = ImplicationGraph::new();
assert_eq!(graph.num_nodes(), 0);
assert_eq!(graph.current_level(), 0);
}
#[test]
fn test_add_decision() {
let mut graph = ImplicationGraph::new();
graph.add_decision(lit(1), 1);
assert_eq!(graph.num_nodes(), 1);
assert_eq!(graph.get_level(lit(1)), Some(1));
assert_eq!(graph.current_level(), 1);
}
#[test]
fn test_add_propagation() {
let mut graph = ImplicationGraph::new();
graph.add_decision(lit(1), 1);
graph.add_propagation(lit(2), 1, vec![lit(1)]);
assert_eq!(graph.num_nodes(), 2);
assert_eq!(graph.get_antecedents(lit(2)), Some(&[lit(1)][..]));
}
#[test]
fn test_get_level() {
let mut graph = ImplicationGraph::new();
graph.add_decision(lit(1), 1);
graph.add_decision(lit(2), 2);
assert_eq!(graph.get_level(lit(1)), Some(1));
assert_eq!(graph.get_level(lit(2)), Some(2));
assert_eq!(graph.get_level(lit(3)), None);
}
#[test]
fn test_backjump_level() {
let mut graph = ImplicationGraph::new();
graph.add_decision(lit(1), 1);
graph.add_decision(lit(2), 2);
graph.add_decision(lit(3), 3);
let clause = vec![lit(1), lit(2), lit(3)];
let level = graph.compute_backjump_level(&clause);
assert_eq!(level, 2); }
#[test]
fn test_backtrack() {
let mut graph = ImplicationGraph::new();
graph.add_decision(lit(1), 1);
graph.add_decision(lit(2), 2);
graph.add_decision(lit(3), 3);
graph.backtrack(1);
assert_eq!(graph.num_nodes(), 1);
assert_eq!(graph.get_level(lit(1)), Some(1));
assert_eq!(graph.get_level(lit(2)), None);
assert_eq!(graph.get_level(lit(3)), None);
}
#[test]
fn test_clear() {
let mut graph = ImplicationGraph::new();
graph.add_decision(lit(1), 1);
graph.add_decision(lit(2), 2);
graph.clear();
assert_eq!(graph.num_nodes(), 0);
assert_eq!(graph.current_level(), 0);
}
#[test]
fn test_find_first_uip() {
let mut graph = ImplicationGraph::new();
graph.add_decision(lit(1), 1);
graph.add_propagation(lit(2), 1, vec![lit(1)]);
graph.add_propagation(lit(3), 1, vec![lit(2)]);
let conflict = vec![lit(3)];
let uip = graph.find_first_uip(&conflict, 1);
assert!(uip.is_some());
}
#[test]
fn test_compaction() {
let config = ImplicationGraphConfig {
enable_compaction: true,
max_size: 2,
};
let mut graph = ImplicationGraph::with_config(config);
graph.add_decision(lit(1), 1);
graph.add_decision(lit(2), 2);
graph.add_decision(lit(3), 3);
assert!(graph.stats().compactions > 0);
}
}