#[allow(unused_imports)]
use crate::prelude::*;
#[derive(Debug, Clone)]
pub struct VarAssignment {
pub var_id: u32,
pub name: String,
pub bool_value: Option<bool>,
pub theory_value: Option<String>,
pub decision_level: u32,
}
#[derive(Debug, Clone)]
pub struct TrailDecision {
pub var_id: u32,
pub value: bool,
pub level: u32,
pub is_propagation: bool,
pub reason_clause: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct ActiveConflict {
pub clause_id: u32,
pub literals: Vec<i64>,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct TheorySolverState {
pub name: String,
pub num_terms: usize,
pub num_equalities: usize,
pub num_disequalities: usize,
pub is_consistent: bool,
pub info: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct StatsSnapshot {
pub decisions: u64,
pub conflicts: u64,
pub propagations: u64,
pub restarts: u64,
pub learned_clauses: u64,
pub theory_propagations: u64,
pub theory_conflicts: u64,
}
#[derive(Debug, Clone)]
pub struct SolverStateSnapshot {
pub label: String,
pub assignments: Vec<VarAssignment>,
pub trail: Vec<TrailDecision>,
pub conflicts: Vec<ActiveConflict>,
pub theory_states: Vec<TheorySolverState>,
pub statistics: StatsSnapshot,
}
impl SolverStateSnapshot {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
assignments: Vec::new(),
trail: Vec::new(),
conflicts: Vec::new(),
theory_states: Vec::new(),
statistics: StatsSnapshot::default(),
}
}
pub fn add_assignment(&mut self, assignment: VarAssignment) {
self.assignments.push(assignment);
}
pub fn add_trail_entry(&mut self, entry: TrailDecision) {
self.trail.push(entry);
}
pub fn add_conflict(&mut self, conflict: ActiveConflict) {
self.conflicts.push(conflict);
}
pub fn add_theory_state(&mut self, state: TheorySolverState) {
self.theory_states.push(state);
}
pub fn set_statistics(&mut self, stats: StatsSnapshot) {
self.statistics = stats;
}
pub fn format_state_text(&self) -> String {
let mut out = String::new();
out.push_str(&format!("=== Solver State: {} ===\n\n", self.label));
out.push_str("--- Statistics ---\n");
out.push_str(&format!(" Decisions: {}\n", self.statistics.decisions));
out.push_str(&format!(" Conflicts: {}\n", self.statistics.conflicts));
out.push_str(&format!(" Propagations: {}\n", self.statistics.propagations));
out.push_str(&format!(" Restarts: {}\n", self.statistics.restarts));
out.push_str(&format!(" Learned clauses: {}\n", self.statistics.learned_clauses));
out.push_str(&format!(
" Theory propagations: {}\n",
self.statistics.theory_propagations
));
out.push_str(&format!(
" Theory conflicts: {}\n",
self.statistics.theory_conflicts
));
out.push('\n');
out.push_str(&format!("--- Assignments ({}) ---\n", self.assignments.len()));
for a in &self.assignments {
let bool_str = a
.bool_value
.map_or_else(|| "?".to_string(), |v| format!("{}", v));
let theory_str = a
.theory_value
.as_deref()
.unwrap_or("-");
out.push_str(&format!(
" {} (v{}): bool={}, theory={}, level={}\n",
a.name, a.var_id, bool_str, theory_str, a.decision_level
));
}
out.push('\n');
out.push_str(&format!("--- Trail ({} entries) ---\n", self.trail.len()));
for (i, t) in self.trail.iter().enumerate() {
let kind = if t.is_propagation { "prop" } else { "decide" };
let reason = t
.reason_clause
.map_or_else(|| "-".to_string(), |c| format!("clause #{}", c));
out.push_str(&format!(
" [{}] v{} = {} ({}, level={}, reason={})\n",
i, t.var_id, t.value, kind, t.level, reason
));
}
out.push('\n');
if !self.conflicts.is_empty() {
out.push_str(&format!("--- Active Conflicts ({}) ---\n", self.conflicts.len()));
for c in &self.conflicts {
out.push_str(&format!(
" Clause #{}: {:?} -- {}\n",
c.clause_id, c.literals, c.description
));
}
out.push('\n');
}
if !self.theory_states.is_empty() {
out.push_str(&format!(
"--- Theory States ({}) ---\n",
self.theory_states.len()
));
for ts in &self.theory_states {
let status = if ts.is_consistent {
"consistent"
} else {
"INCONSISTENT"
};
out.push_str(&format!(
" {}: terms={}, eq={}, diseq={}, status={}\n",
ts.name, ts.num_terms, ts.num_equalities, ts.num_disequalities, status
));
for info in &ts.info {
out.push_str(&format!(" {}\n", info));
}
}
}
out
}
pub fn format_state_dot(&self) -> String {
let mut dot = ImplicationGraphDot::new("solver_state");
for t in &self.trail {
let label = format!(
"v{} = {}\\nlevel={}",
t.var_id, t.value, t.level
);
if t.is_propagation {
dot.add_propagation_node(t.var_id, &label);
} else {
dot.add_decision_node(t.var_id, &label);
}
}
for t in &self.trail {
if let Some(clause_id) = t.reason_clause {
let clause_node_id = 100_000 + clause_id;
dot.add_clause_node(clause_node_id, &format!("clause #{}", clause_id));
dot.add_edge(clause_node_id, t.var_id, "reason");
}
}
for c in &self.conflicts {
let conflict_node_id = 200_000 + c.clause_id;
dot.add_conflict_node(conflict_node_id, &format!("CONFLICT\\n{}", c.description));
for &lit in &c.literals {
let var_id = lit.unsigned_abs() as u32;
dot.add_edge(var_id, conflict_node_id, "conflict");
}
}
dot.to_dot()
}
}
#[derive(Debug, Clone)]
enum DotNodeKind {
Decision,
Propagation,
Clause,
Conflict,
}
#[derive(Debug, Clone)]
struct DotNode {
id: u32,
label: String,
kind: DotNodeKind,
}
#[derive(Debug, Clone)]
struct DotEdge {
from: u32,
to: u32,
label: String,
}
#[derive(Debug)]
pub struct ImplicationGraphDot {
name: String,
nodes: Vec<DotNode>,
edges: Vec<DotEdge>,
}
impl ImplicationGraphDot {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
nodes: Vec::new(),
edges: Vec::new(),
}
}
pub fn add_decision_node(&mut self, id: u32, label: &str) {
self.nodes.push(DotNode {
id,
label: label.to_string(),
kind: DotNodeKind::Decision,
});
}
pub fn add_propagation_node(&mut self, id: u32, label: &str) {
self.nodes.push(DotNode {
id,
label: label.to_string(),
kind: DotNodeKind::Propagation,
});
}
pub fn add_clause_node(&mut self, id: u32, label: &str) {
self.nodes.push(DotNode {
id,
label: label.to_string(),
kind: DotNodeKind::Clause,
});
}
pub fn add_conflict_node(&mut self, id: u32, label: &str) {
self.nodes.push(DotNode {
id,
label: label.to_string(),
kind: DotNodeKind::Conflict,
});
}
pub fn add_edge(&mut self, from: u32, to: u32, label: &str) {
self.edges.push(DotEdge {
from,
to,
label: label.to_string(),
});
}
pub fn to_dot(&self) -> String {
let mut out = String::new();
out.push_str(&format!("digraph {} {{\n", self.name));
out.push_str(" rankdir=LR;\n");
out.push_str(" node [fontname=\"Helvetica\"];\n\n");
for node in &self.nodes {
let (shape, color, style) = match node.kind {
DotNodeKind::Decision => ("box", "blue", "filled"),
DotNodeKind::Propagation => ("ellipse", "green", "filled"),
DotNodeKind::Clause => ("diamond", "gray", "filled"),
DotNodeKind::Conflict => ("octagon", "red", "filled"),
};
out.push_str(&format!(
" n{} [label=\"{}\", shape={}, color={}, style={}, fillcolor=\"{}30\"];\n",
node.id, node.label, shape, color, style, color
));
}
out.push('\n');
for edge in &self.edges {
if edge.label.is_empty() {
out.push_str(&format!(" n{} -> n{};\n", edge.from, edge.to));
} else {
out.push_str(&format!(
" n{} -> n{} [label=\"{}\"];\n",
edge.from, edge.to, edge.label
));
}
}
out.push_str("}\n");
out
}
pub fn from_implication_data(
implications: &[(i64, u32, Vec<i64>, bool)],
conflict_clause: &[i64],
) -> Self {
let mut dot = Self::new("implication_graph");
for &(lit, level, ref _antes, is_decision) in implications {
let var_id = lit.unsigned_abs() as u32;
let polarity = if lit > 0 { "T" } else { "F" };
let label = format!("x{} = {}\\nlevel={}", var_id, polarity, level);
if is_decision {
dot.add_decision_node(var_id, &label);
} else {
dot.add_propagation_node(var_id, &label);
}
}
for &(lit, _level, ref antes, _is_decision) in implications {
let to = lit.unsigned_abs() as u32;
for &ante in antes {
let from = ante.unsigned_abs() as u32;
dot.add_edge(from, to, "");
}
}
if !conflict_clause.is_empty() {
let conflict_id = 999_999;
dot.add_conflict_node(conflict_id, "CONFLICT");
for &lit in conflict_clause {
let var_id = lit.unsigned_abs() as u32;
dot.add_edge(var_id, conflict_id, "");
}
}
dot
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_snapshot() {
let snap = SolverStateSnapshot::new("test");
assert_eq!(snap.label, "test");
assert!(snap.assignments.is_empty());
assert!(snap.trail.is_empty());
assert!(snap.conflicts.is_empty());
assert!(snap.theory_states.is_empty());
}
#[test]
fn test_snapshot_add_assignment() {
let mut snap = SolverStateSnapshot::new("assign_test");
snap.add_assignment(VarAssignment {
var_id: 1,
name: "x".to_string(),
bool_value: Some(true),
theory_value: None,
decision_level: 0,
});
snap.add_assignment(VarAssignment {
var_id: 2,
name: "y".to_string(),
bool_value: Some(false),
theory_value: Some("42".to_string()),
decision_level: 1,
});
assert_eq!(snap.assignments.len(), 2);
assert_eq!(snap.assignments[0].name, "x");
assert_eq!(snap.assignments[1].theory_value.as_deref(), Some("42"));
}
#[test]
fn test_snapshot_format_text() {
let mut snap = SolverStateSnapshot::new("text_test");
snap.set_statistics(StatsSnapshot {
decisions: 10,
conflicts: 3,
propagations: 50,
restarts: 1,
learned_clauses: 3,
theory_propagations: 5,
theory_conflicts: 1,
});
snap.add_assignment(VarAssignment {
var_id: 1,
name: "p".to_string(),
bool_value: Some(true),
theory_value: None,
decision_level: 0,
});
snap.add_trail_entry(TrailDecision {
var_id: 1,
value: true,
level: 0,
is_propagation: false,
reason_clause: None,
});
let text = snap.format_state_text();
assert!(text.contains("Solver State: text_test"));
assert!(text.contains("Decisions: 10"));
assert!(text.contains("p (v1): bool=true"));
assert!(text.contains("v1 = true (decide"));
}
#[test]
fn test_snapshot_format_text_with_conflict() {
let mut snap = SolverStateSnapshot::new("conflict_test");
snap.add_conflict(ActiveConflict {
clause_id: 7,
literals: vec![1, -2, 3],
description: "Theory conflict in EUF".to_string(),
});
let text = snap.format_state_text();
assert!(text.contains("Active Conflicts"));
assert!(text.contains("Clause #7"));
assert!(text.contains("Theory conflict in EUF"));
}
#[test]
fn test_snapshot_format_dot() {
let mut snap = SolverStateSnapshot::new("dot_test");
snap.add_trail_entry(TrailDecision {
var_id: 1,
value: true,
level: 0,
is_propagation: false,
reason_clause: None,
});
snap.add_trail_entry(TrailDecision {
var_id: 2,
value: false,
level: 0,
is_propagation: true,
reason_clause: Some(5),
});
let dot = snap.format_state_dot();
assert!(dot.contains("digraph solver_state"));
assert!(dot.contains("n1"));
assert!(dot.contains("n2"));
assert!(dot.contains("shape=box")); assert!(dot.contains("shape=ellipse")); }
#[test]
fn test_implication_graph_dot_empty() {
let dot = ImplicationGraphDot::new("empty");
let output = dot.to_dot();
assert!(output.contains("digraph empty"));
assert!(output.contains('}'));
}
#[test]
fn test_implication_graph_dot_from_data() {
let implications = vec![
(1_i64, 0_u32, vec![], true), (2, 0, vec![1], false), (-3, 1, vec![], true), (4, 1, vec![2, -3], false), ];
let conflict_clause = vec![4, -2];
let dot = ImplicationGraphDot::from_implication_data(&implications, &conflict_clause);
let output = dot.to_dot();
assert!(output.contains("digraph implication_graph"));
assert!(output.contains("CONFLICT"));
assert!(output.contains("shape=box")); assert!(output.contains("shape=ellipse")); assert!(output.contains("shape=octagon")); }
#[test]
fn test_theory_state_display() {
let mut snap = SolverStateSnapshot::new("theory_test");
snap.add_theory_state(TheorySolverState {
name: "EUF".to_string(),
num_terms: 15,
num_equalities: 3,
num_disequalities: 2,
is_consistent: true,
info: vec!["Congruence classes: 5".to_string()],
});
snap.add_theory_state(TheorySolverState {
name: "LRA".to_string(),
num_terms: 8,
num_equalities: 1,
num_disequalities: 0,
is_consistent: false,
info: vec![],
});
let text = snap.format_state_text();
assert!(text.contains("EUF: terms=15"));
assert!(text.contains("consistent"));
assert!(text.contains("LRA: terms=8"));
assert!(text.contains("INCONSISTENT"));
assert!(text.contains("Congruence classes: 5"));
}
#[test]
fn test_dot_edge_labels() {
let mut dot = ImplicationGraphDot::new("edges");
dot.add_decision_node(1, "x1");
dot.add_propagation_node(2, "x2");
dot.add_edge(1, 2, "unit");
dot.add_edge(2, 1, "");
let output = dot.to_dot();
assert!(output.contains("n1 -> n2 [label=\"unit\"]"));
assert!(output.contains("n2 -> n1;"));
}
}