use crate::literal::{Lit, Var};
#[allow(unused_imports)]
use crate::prelude::*;
#[derive(Debug, Clone)]
pub struct ResolutionNode {
id: usize,
clause: Option<Vec<Lit>>,
parents: Vec<usize>,
resolved_var: Option<Var>,
decision_level: usize,
is_decision: bool,
}
impl ResolutionNode {
pub fn new(id: usize, clause: Vec<Lit>, decision_level: usize) -> Self {
Self {
id,
clause: Some(clause),
parents: Vec::new(),
resolved_var: None,
decision_level,
is_decision: false,
}
}
pub fn decision(id: usize, literal: Lit, decision_level: usize) -> Self {
Self {
id,
clause: Some(vec![literal]),
parents: Vec::new(),
resolved_var: None,
decision_level,
is_decision: true,
}
}
pub fn add_resolution(&mut self, parent1: usize, parent2: usize, resolved_var: Var) {
self.parents.push(parent1);
self.parents.push(parent2);
self.resolved_var = Some(resolved_var);
}
pub fn clause(&self) -> Option<&[Lit]> {
self.clause.as_deref()
}
pub fn parents(&self) -> &[usize] {
&self.parents
}
pub fn resolved_var(&self) -> Option<Var> {
self.resolved_var
}
pub fn is_decision(&self) -> bool {
self.is_decision
}
pub fn decision_level(&self) -> usize {
self.decision_level
}
}
#[derive(Debug)]
pub struct ResolutionGraph {
nodes: Vec<ResolutionNode>,
clause_map: HashMap<u64, usize>,
stats: GraphStats,
}
#[derive(Debug, Default, Clone)]
pub struct GraphStats {
pub resolutions: usize,
pub decisions: usize,
pub max_depth: usize,
pub avg_parents: f64,
pub frequent_vars: HashMap<Var, usize>,
}
impl ResolutionGraph {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
clause_map: HashMap::new(),
stats: GraphStats::default(),
}
}
pub fn add_clause(&mut self, clause: Vec<Lit>, decision_level: usize) -> usize {
let hash = Self::hash_clause(&clause);
if let Some(&node_id) = self.clause_map.get(&hash) {
return node_id;
}
let node_id = self.nodes.len();
let node = ResolutionNode::new(node_id, clause, decision_level);
self.nodes.push(node);
self.clause_map.insert(hash, node_id);
node_id
}
pub fn add_decision(&mut self, literal: Lit, decision_level: usize) -> usize {
let node_id = self.nodes.len();
let node = ResolutionNode::decision(node_id, literal, decision_level);
self.nodes.push(node);
self.stats.decisions += 1;
node_id
}
pub fn add_resolution(
&mut self,
parent1_id: usize,
parent2_id: usize,
resolved_var: Var,
result_clause: Vec<Lit>,
decision_level: usize,
) -> usize {
let result_id = self.add_clause(result_clause, decision_level);
if let Some(node) = self.nodes.get_mut(result_id)
&& node.parents.is_empty()
{
node.add_resolution(parent1_id, parent2_id, resolved_var);
self.stats.resolutions += 1;
*self.stats.frequent_vars.entry(resolved_var).or_insert(0) += 1;
}
result_id
}
pub fn compute_depth(&self, node_id: usize) -> usize {
let mut visited = HashSet::new();
self.compute_depth_recursive(node_id, &mut visited)
}
fn compute_depth_recursive(&self, node_id: usize, visited: &mut HashSet<usize>) -> usize {
if visited.contains(&node_id) {
return 0; }
visited.insert(node_id);
let node = &self.nodes[node_id];
if node.parents.is_empty() {
return 1; }
let max_parent_depth = node
.parents
.iter()
.map(|&parent_id| self.compute_depth_recursive(parent_id, visited))
.max()
.unwrap_or(0);
max_parent_depth + 1
}
pub fn analyze(&mut self) {
if self.nodes.is_empty() {
return;
}
self.stats.max_depth = (0..self.nodes.len())
.map(|id| self.compute_depth(id))
.max()
.unwrap_or(0);
let total_parents: usize = self.nodes.iter().map(|n| n.parents.len()).sum();
self.stats.avg_parents = total_parents as f64 / self.nodes.len() as f64;
}
pub fn get_frequent_vars(&self, k: usize) -> Vec<(Var, usize)> {
let mut vars: Vec<_> = self
.stats
.frequent_vars
.iter()
.map(|(&var, &count)| (var, count))
.collect();
vars.sort_by_key(|item| std::cmp::Reverse(item.1));
vars.truncate(k);
vars
}
pub fn clause_quality(&self, node_id: usize) -> f64 {
if node_id >= self.nodes.len() {
return f64::MAX;
}
let node = &self.nodes[node_id];
let depth = self.compute_depth(node_id) as f64;
let decision_level = node.decision_level as f64;
let freq_score = if let Some(clause) = node.clause() {
clause
.iter()
.filter_map(|lit| {
self.stats
.frequent_vars
.get(&lit.var())
.map(|&count| count as f64)
})
.sum::<f64>()
} else {
0.0
};
depth + decision_level / (1.0 + freq_score)
}
pub fn find_redundant_resolutions(&self) -> Vec<usize> {
let mut redundant = Vec::new();
for (i, node) in self.nodes.iter().enumerate() {
if node.parents.len() < 2 {
continue; }
if self.has_shorter_path(i) {
redundant.push(i);
}
}
redundant
}
fn has_shorter_path(&self, node_id: usize) -> bool {
let node = &self.nodes[node_id];
let Some(target_clause) = node.clause() else {
return false;
};
let target_hash = Self::hash_clause(target_clause);
let mut queue = VecDeque::new();
let mut visited = HashSet::new();
let mut depths = HashMap::new();
for (id, n) in self.nodes.iter().enumerate() {
if n.parents.is_empty() && id != node_id {
queue.push_back(id);
depths.insert(id, 0);
}
}
while let Some(current_id) = queue.pop_front() {
if visited.contains(¤t_id) {
continue;
}
visited.insert(current_id);
let current_depth = depths[¤t_id];
if let Some(clause) = self.nodes[current_id].clause()
&& Self::hash_clause(clause) == target_hash
&& current_depth < self.compute_depth(node_id)
{
return true; }
for (child_id, child) in self.nodes.iter().enumerate() {
if child.parents.contains(¤t_id) && !visited.contains(&child_id) {
queue.push_back(child_id);
depths.insert(child_id, current_depth + 1);
}
}
}
false
}
fn hash_clause(clause: &[Lit]) -> u64 {
use core::hash::BuildHasher;
let mut sorted = clause.to_vec();
sorted.sort_unstable_by_key(|lit| lit.code());
let build = core::hash::BuildHasherDefault::<rustc_hash::FxHasher>::default();
build.hash_one(&sorted)
}
pub fn stats(&self) -> &GraphStats {
&self.stats
}
pub fn clear(&mut self) {
self.nodes.clear();
self.clause_map.clear();
self.stats = GraphStats::default();
}
pub fn num_nodes(&self) -> usize {
self.nodes.len()
}
pub fn get_node(&self, node_id: usize) -> Option<&ResolutionNode> {
self.nodes.get(node_id)
}
}
impl Default for ResolutionGraph {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ResolutionAnalyzer {
graph: ResolutionGraph,
var_scores: HashMap<Var, f64>,
enabled: bool,
}
impl ResolutionAnalyzer {
pub fn new() -> Self {
Self {
graph: ResolutionGraph::new(),
var_scores: HashMap::new(),
enabled: true,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn graph(&self) -> &ResolutionGraph {
&self.graph
}
pub fn graph_mut(&mut self) -> &mut ResolutionGraph {
&mut self.graph
}
pub fn analyze(&mut self) {
if !self.enabled {
return;
}
self.graph.analyze();
self.var_scores.clear();
for (&var, &count) in &self.graph.stats.frequent_vars {
let frequency_score = count as f64;
let quality_score: f64 = self
.graph
.nodes
.iter()
.filter(|node| {
node.clause()
.map(|c| c.iter().any(|lit| lit.var() == var))
.unwrap_or(false)
})
.map(|node| 1.0 / (1.0 + self.graph.clause_quality(node.id)))
.sum();
self.var_scores.insert(var, frequency_score + quality_score);
}
}
pub fn variable_importance(&self, var: Var) -> f64 {
self.var_scores.get(&var).copied().unwrap_or(0.0)
}
pub fn get_important_vars(&self, k: usize) -> Vec<(Var, f64)> {
let mut vars: Vec<_> = self
.var_scores
.iter()
.map(|(&var, &score)| (var, score))
.collect();
vars.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal));
vars.truncate(k);
vars
}
pub fn clear(&mut self) {
self.graph.clear();
self.var_scores.clear();
}
pub fn stats(&self) -> &GraphStats {
self.graph.stats()
}
}
impl Default for ResolutionAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolution_graph_creation() {
let graph = ResolutionGraph::new();
assert_eq!(graph.num_nodes(), 0);
}
#[test]
fn test_add_clause() {
let mut graph = ResolutionGraph::new();
let v0 = Var(0);
let v1 = Var(1);
let clause1 = vec![Lit::pos(v0), Lit::pos(v1)];
let id1 = graph.add_clause(clause1.clone(), 0);
assert_eq!(id1, 0);
assert_eq!(graph.num_nodes(), 1);
let id2 = graph.add_clause(clause1, 0);
assert_eq!(id1, id2);
assert_eq!(graph.num_nodes(), 1);
}
#[test]
fn test_add_decision() {
let mut graph = ResolutionGraph::new();
let v0 = Var(0);
let id = graph.add_decision(Lit::pos(v0), 1);
assert_eq!(id, 0);
assert_eq!(graph.num_nodes(), 1);
let node = graph
.get_node(id)
.expect("Decision node must exist in graph");
assert!(node.is_decision());
assert_eq!(node.decision_level(), 1);
}
#[test]
fn test_resolution() {
let mut graph = ResolutionGraph::new();
let v0 = Var(0);
let v1 = Var(1);
let clause1 = vec![Lit::pos(v0), Lit::pos(v1)];
let id1 = graph.add_clause(clause1, 0);
let clause2 = vec![Lit::neg(v0), Lit::pos(v1)];
let id2 = graph.add_clause(clause2, 0);
let result = vec![Lit::pos(v1)];
let id3 = graph.add_resolution(id1, id2, v0, result, 1);
assert_eq!(graph.num_nodes(), 3);
let node = graph
.get_node(id3)
.expect("Resolution node must exist in graph");
assert_eq!(node.parents().len(), 2);
assert_eq!(node.resolved_var(), Some(v0));
assert_eq!(graph.stats().resolutions, 1);
}
#[test]
fn test_compute_depth() {
let mut graph = ResolutionGraph::new();
let v0 = Var(0);
let v1 = Var(1);
let id1 = graph.add_clause(vec![Lit::pos(v0)], 0);
let id2 = graph.add_clause(vec![Lit::neg(v0), Lit::pos(v1)], 0);
let id3 = graph.add_resolution(id1, id2, v0, vec![Lit::pos(v1)], 1);
assert_eq!(graph.compute_depth(id1), 1); assert_eq!(graph.compute_depth(id2), 1); assert_eq!(graph.compute_depth(id3), 2); }
#[test]
fn test_analyze() {
let mut graph = ResolutionGraph::new();
let v0 = Var(0);
let v1 = Var(1);
let id1 = graph.add_clause(vec![Lit::pos(v0)], 0);
let id2 = graph.add_clause(vec![Lit::neg(v0), Lit::pos(v1)], 0);
graph.add_resolution(id1, id2, v0, vec![Lit::pos(v1)], 1);
graph.analyze();
assert_eq!(graph.stats().resolutions, 1);
assert!(graph.stats().max_depth > 0);
}
#[test]
fn test_frequent_vars() {
let mut graph = ResolutionGraph::new();
let v0 = Var(0);
let v1 = Var(1);
let v2 = Var(2);
let id1 = graph.add_clause(vec![Lit::pos(v0)], 0);
let id2 = graph.add_clause(vec![Lit::neg(v0), Lit::pos(v1)], 0);
graph.add_resolution(id1, id2, v0, vec![Lit::pos(v1)], 1);
let id3 = graph.add_clause(vec![Lit::pos(v0), Lit::pos(v2)], 0);
let id4 = graph.add_clause(vec![Lit::neg(v0)], 0);
graph.add_resolution(id3, id4, v0, vec![Lit::pos(v2)], 1);
let freq = graph.get_frequent_vars(10);
assert!(!freq.is_empty());
assert_eq!(freq[0].0, v0); assert_eq!(freq[0].1, 2); }
#[test]
fn test_resolution_analyzer() {
let mut analyzer = ResolutionAnalyzer::new();
assert!(analyzer.is_enabled());
let v0 = Var(0);
let v1 = Var(1);
let id1 = analyzer.graph_mut().add_clause(vec![Lit::pos(v0)], 0);
let id2 = analyzer
.graph_mut()
.add_clause(vec![Lit::neg(v0), Lit::pos(v1)], 0);
analyzer
.graph_mut()
.add_resolution(id1, id2, v0, vec![Lit::pos(v1)], 1);
analyzer.analyze();
assert!(analyzer.variable_importance(v0) > 0.0);
}
#[test]
fn test_important_vars() {
let mut analyzer = ResolutionAnalyzer::new();
let v0 = Var(0);
let v1 = Var(1);
let v2 = Var(2);
let id1 = analyzer.graph_mut().add_clause(vec![Lit::pos(v0)], 0);
let id2 = analyzer
.graph_mut()
.add_clause(vec![Lit::neg(v0), Lit::pos(v1)], 0);
analyzer
.graph_mut()
.add_resolution(id1, id2, v0, vec![Lit::pos(v1)], 1);
let id3 = analyzer
.graph_mut()
.add_clause(vec![Lit::pos(v0), Lit::pos(v2)], 0);
let id4 = analyzer.graph_mut().add_clause(vec![Lit::neg(v0)], 0);
analyzer
.graph_mut()
.add_resolution(id3, id4, v0, vec![Lit::pos(v2)], 1);
analyzer.analyze();
let important = analyzer.get_important_vars(2);
assert!(!important.is_empty());
assert_eq!(important[0].0, v0); }
#[test]
fn test_clear() {
let mut analyzer = ResolutionAnalyzer::new();
let v0 = Var(0);
analyzer.graph_mut().add_clause(vec![Lit::pos(v0)], 0);
assert_eq!(analyzer.graph().num_nodes(), 1);
analyzer.clear();
assert_eq!(analyzer.graph().num_nodes(), 0);
}
#[test]
fn test_clause_quality() {
let mut graph = ResolutionGraph::new();
let v0 = Var(0);
let v1 = Var(1);
let id1 = graph.add_clause(vec![Lit::pos(v0)], 0);
let id2 = graph.add_clause(vec![Lit::neg(v0), Lit::pos(v1)], 0);
let id3 = graph.add_resolution(id1, id2, v0, vec![Lit::pos(v1)], 1);
graph.analyze();
let quality1 = graph.clause_quality(id1);
let quality3 = graph.clause_quality(id3);
assert!(quality1 <= quality3);
}
#[test]
fn test_disabled_analyzer() {
let mut analyzer = ResolutionAnalyzer::new();
analyzer.set_enabled(false);
assert!(!analyzer.is_enabled());
let v0 = Var(0);
analyzer.graph_mut().add_clause(vec![Lit::pos(v0)], 0);
analyzer.analyze();
assert!(analyzer.var_scores.is_empty());
}
}