#[allow(unused_imports)]
use crate::prelude::*;
use oxiz_core::ast::TermId;
pub struct BacktrackManager {
decision_stack: Vec<DecisionLevel>,
current_level: usize,
trail: Vec<Assignment>,
var_to_trail: FxHashMap<TermId, usize>,
undo_stack: Vec<UndoAction>,
checkpoints: Vec<Checkpoint>,
stats: BacktrackStats,
}
#[derive(Debug, Clone)]
pub struct DecisionLevel {
pub level: usize,
pub decision: Option<Assignment>,
pub trail_start: usize,
}
#[derive(Debug, Clone)]
pub struct Assignment {
pub var: TermId,
pub value: bool,
pub level: usize,
pub reason: Option<ReasonClause>,
}
#[derive(Debug, Clone)]
pub enum ReasonClause {
BooleanClause(Vec<TermId>),
TheoryPropagation(TheoryReason),
}
#[derive(Debug, Clone)]
pub struct TheoryReason {
pub theory_id: usize,
pub explanation: Vec<TermId>,
}
#[derive(Debug, Clone)]
pub enum UndoAction {
UndoEquality(TermId, TermId),
UndoBound(TermId, BoundUpdate),
UndoArrayStore(TermId),
TheoryUndo(usize, Vec<TermId>),
}
#[derive(Debug, Clone)]
pub struct BoundUpdate {
pub old_lower: Option<i64>,
pub old_upper: Option<i64>,
}
#[derive(Debug, Clone)]
pub struct Checkpoint {
pub level: usize,
pub trail_size: usize,
pub undo_size: usize,
}
#[derive(Debug, Clone, Default)]
pub struct BacktrackStats {
pub decisions: usize,
pub propagations: usize,
pub backtracks: usize,
pub restarts: usize,
pub undos: usize,
pub max_level: usize,
}
impl BacktrackManager {
pub fn new() -> Self {
Self {
decision_stack: vec![DecisionLevel {
level: 0,
decision: None,
trail_start: 0,
}],
current_level: 0,
trail: Vec::new(),
var_to_trail: FxHashMap::default(),
undo_stack: Vec::new(),
checkpoints: Vec::new(),
stats: BacktrackStats::default(),
}
}
pub fn decide(&mut self, var: TermId, value: bool) -> Result<(), String> {
self.current_level += 1;
self.stats.decisions += 1;
if self.current_level > self.stats.max_level {
self.stats.max_level = self.current_level;
}
let assignment = Assignment {
var,
value,
level: self.current_level,
reason: None, };
let trail_start = self.trail.len();
self.decision_stack.push(DecisionLevel {
level: self.current_level,
decision: Some(assignment.clone()),
trail_start,
});
self.assign(assignment)?;
Ok(())
}
pub fn propagate(
&mut self,
var: TermId,
value: bool,
reason: ReasonClause,
) -> Result<(), String> {
self.stats.propagations += 1;
let assignment = Assignment {
var,
value,
level: self.current_level,
reason: Some(reason),
};
self.assign(assignment)?;
Ok(())
}
fn assign(&mut self, assignment: Assignment) -> Result<(), String> {
if let Some(&trail_idx) = self.var_to_trail.get(&assignment.var) {
let existing = &self.trail[trail_idx];
if existing.value != assignment.value {
return Err(format!(
"Conflict: variable {:?} already assigned differently",
assignment.var
));
}
return Ok(());
}
let trail_idx = self.trail.len();
self.var_to_trail.insert(assignment.var, trail_idx);
self.trail.push(assignment);
Ok(())
}
pub fn backtrack(&mut self, target_level: usize) -> Result<(), String> {
if target_level > self.current_level {
return Err("Cannot backtrack to higher level".to_string());
}
if target_level == self.current_level {
return Ok(()); }
self.stats.backtracks += 1;
let target_trail_pos = if target_level + 1 < self.decision_stack.len() {
self.decision_stack[target_level + 1].trail_start
} else {
self.trail.len()
};
while self.trail.len() > target_trail_pos {
if let Some(assignment) = self.trail.pop() {
self.var_to_trail.remove(&assignment.var);
}
}
self.undo_to_level(target_level)?;
self.decision_stack.truncate(target_level + 1);
self.current_level = target_level;
Ok(())
}
pub fn restart(&mut self) -> Result<(), String> {
self.stats.restarts += 1;
self.backtrack(0)
}
pub fn record_undo(&mut self, action: UndoAction) {
self.undo_stack.push(action);
}
fn undo_to_level(&mut self, _target_level: usize) -> Result<(), String> {
let mut undos_to_apply = Vec::new();
while let Some(undo) = self.undo_stack.pop() {
undos_to_apply.push(undo);
if undos_to_apply.len() > 100 {
break;
}
}
for undo in undos_to_apply {
self.apply_undo(undo)?;
self.stats.undos += 1;
}
Ok(())
}
fn apply_undo(&mut self, _action: UndoAction) -> Result<(), String> {
Ok(())
}
pub fn push_checkpoint(&mut self) {
self.checkpoints.push(Checkpoint {
level: self.current_level,
trail_size: self.trail.len(),
undo_size: self.undo_stack.len(),
});
}
pub fn pop_checkpoint(&mut self) -> Result<(), String> {
if let Some(checkpoint) = self.checkpoints.pop() {
self.backtrack(checkpoint.level)?;
self.undo_stack.truncate(checkpoint.undo_size);
Ok(())
} else {
Err("No checkpoint to pop".to_string())
}
}
pub fn current_level(&self) -> usize {
self.current_level
}
pub fn is_assigned(&self, var: TermId) -> bool {
self.var_to_trail.contains_key(&var)
}
pub fn get_assignment(&self, var: TermId) -> Option<&Assignment> {
if let Some(&trail_idx) = self.var_to_trail.get(&var) {
self.trail.get(trail_idx)
} else {
None
}
}
pub fn current_assignments(&self) -> &[Assignment] {
&self.trail
}
pub fn get_decision(&self, level: usize) -> Option<&Assignment> {
if level < self.decision_stack.len() {
self.decision_stack[level].decision.as_ref()
} else {
None
}
}
pub fn stats(&self) -> &BacktrackStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = BacktrackStats::default();
}
}
impl Default for BacktrackManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backtrack_manager() {
let mgr = BacktrackManager::new();
assert_eq!(mgr.current_level(), 0);
assert_eq!(mgr.stats.decisions, 0);
}
#[test]
fn test_decide() {
let mut mgr = BacktrackManager::new();
let var = TermId::from(1);
mgr.decide(var, true)
.expect("test operation should succeed");
assert_eq!(mgr.current_level(), 1);
assert_eq!(mgr.stats.decisions, 1);
assert!(mgr.is_assigned(var));
}
#[test]
fn test_propagate() {
let mut mgr = BacktrackManager::new();
let var1 = TermId::from(1);
mgr.decide(var1, true)
.expect("test operation should succeed");
let var2 = TermId::from(2);
let reason = ReasonClause::BooleanClause(vec![var1]);
mgr.propagate(var2, false, reason)
.expect("test operation should succeed");
assert_eq!(mgr.stats.propagations, 1);
assert!(mgr.is_assigned(var2));
}
#[test]
fn test_backtrack() {
let mut mgr = BacktrackManager::new();
let var1 = TermId::from(1);
mgr.decide(var1, true)
.expect("test operation should succeed");
let var2 = TermId::from(2);
mgr.decide(var2, false)
.expect("test operation should succeed");
assert_eq!(mgr.current_level(), 2);
mgr.backtrack(1).expect("test operation should succeed");
assert_eq!(mgr.current_level(), 1);
assert!(mgr.is_assigned(var1));
assert!(!mgr.is_assigned(var2));
}
#[test]
fn test_restart() {
let mut mgr = BacktrackManager::new();
mgr.decide(TermId::from(1), true)
.expect("test operation should succeed");
mgr.decide(TermId::from(2), false)
.expect("test operation should succeed");
mgr.restart().expect("test operation should succeed");
assert_eq!(mgr.current_level(), 0);
assert_eq!(mgr.stats.restarts, 1);
assert_eq!(mgr.trail.len(), 0);
}
#[test]
fn test_checkpoint() {
let mut mgr = BacktrackManager::new();
mgr.decide(TermId::from(1), true)
.expect("test operation should succeed");
mgr.push_checkpoint();
mgr.decide(TermId::from(2), false)
.expect("test operation should succeed");
mgr.pop_checkpoint().expect("test operation should succeed");
assert_eq!(mgr.current_level(), 1);
assert!(!mgr.is_assigned(TermId::from(2)));
}
#[test]
fn test_get_assignment() {
let mut mgr = BacktrackManager::new();
let var = TermId::from(1);
mgr.decide(var, true)
.expect("test operation should succeed");
let assignment = mgr
.get_assignment(var)
.expect("test operation should succeed");
assert!(assignment.value);
assert_eq!(assignment.level, 1);
assert!(assignment.reason.is_none());
}
}