1#[allow(unused_imports)]
10use crate::prelude::*;
11use oxiz_core::ast::TermId;
12
13pub struct BacktrackManager {
15 decision_stack: Vec<DecisionLevel>,
17 current_level: usize,
19 trail: Vec<Assignment>,
21 var_to_trail: FxHashMap<TermId, usize>,
23 undo_stack: Vec<UndoAction>,
25 checkpoints: Vec<Checkpoint>,
27 stats: BacktrackStats,
29}
30
31#[derive(Debug, Clone)]
33pub struct DecisionLevel {
34 pub level: usize,
36 pub decision: Option<Assignment>,
38 pub trail_start: usize,
40}
41
42#[derive(Debug, Clone)]
44pub struct Assignment {
45 pub var: TermId,
47 pub value: bool,
49 pub level: usize,
51 pub reason: Option<ReasonClause>,
53}
54
55#[derive(Debug, Clone)]
57pub enum ReasonClause {
58 BooleanClause(Vec<TermId>),
60 TheoryPropagation(TheoryReason),
62}
63
64#[derive(Debug, Clone)]
66pub struct TheoryReason {
67 pub theory_id: usize,
69 pub explanation: Vec<TermId>,
71}
72
73#[derive(Debug, Clone)]
75pub enum UndoAction {
76 UndoEquality(TermId, TermId),
78 UndoBound(TermId, BoundUpdate),
80 UndoArrayStore(TermId),
82 TheoryUndo(usize, Vec<TermId>),
84}
85
86#[derive(Debug, Clone)]
88pub struct BoundUpdate {
89 pub old_lower: Option<i64>,
91 pub old_upper: Option<i64>,
93}
94
95#[derive(Debug, Clone)]
97pub struct Checkpoint {
98 pub level: usize,
100 pub trail_size: usize,
102 pub undo_size: usize,
104}
105
106#[derive(Debug, Clone, Default)]
108pub struct BacktrackStats {
109 pub decisions: usize,
111 pub propagations: usize,
113 pub backtracks: usize,
115 pub restarts: usize,
117 pub undos: usize,
119 pub max_level: usize,
121}
122
123impl BacktrackManager {
124 pub fn new() -> Self {
126 Self {
127 decision_stack: vec![DecisionLevel {
128 level: 0,
129 decision: None,
130 trail_start: 0,
131 }],
132 current_level: 0,
133 trail: Vec::new(),
134 var_to_trail: FxHashMap::default(),
135 undo_stack: Vec::new(),
136 checkpoints: Vec::new(),
137 stats: BacktrackStats::default(),
138 }
139 }
140
141 pub fn decide(&mut self, var: TermId, value: bool) -> Result<(), String> {
143 self.current_level += 1;
144 self.stats.decisions += 1;
145
146 if self.current_level > self.stats.max_level {
147 self.stats.max_level = self.current_level;
148 }
149
150 let assignment = Assignment {
151 var,
152 value,
153 level: self.current_level,
154 reason: None, };
156
157 let trail_start = self.trail.len();
159
160 self.decision_stack.push(DecisionLevel {
162 level: self.current_level,
163 decision: Some(assignment.clone()),
164 trail_start,
165 });
166
167 self.assign(assignment)?;
169
170 Ok(())
171 }
172
173 pub fn propagate(
175 &mut self,
176 var: TermId,
177 value: bool,
178 reason: ReasonClause,
179 ) -> Result<(), String> {
180 self.stats.propagations += 1;
181
182 let assignment = Assignment {
183 var,
184 value,
185 level: self.current_level,
186 reason: Some(reason),
187 };
188
189 self.assign(assignment)?;
190
191 Ok(())
192 }
193
194 fn assign(&mut self, assignment: Assignment) -> Result<(), String> {
196 if let Some(&trail_idx) = self.var_to_trail.get(&assignment.var) {
198 let existing = &self.trail[trail_idx];
199 if existing.value != assignment.value {
200 return Err(format!(
201 "Conflict: variable {:?} already assigned differently",
202 assignment.var
203 ));
204 }
205 return Ok(());
207 }
208
209 let trail_idx = self.trail.len();
211 self.var_to_trail.insert(assignment.var, trail_idx);
212
213 self.trail.push(assignment);
215
216 Ok(())
217 }
218
219 pub fn backtrack(&mut self, target_level: usize) -> Result<(), String> {
221 if target_level > self.current_level {
222 return Err("Cannot backtrack to higher level".to_string());
223 }
224
225 if target_level == self.current_level {
226 return Ok(()); }
228
229 self.stats.backtracks += 1;
230
231 let target_trail_pos = if target_level + 1 < self.decision_stack.len() {
234 self.decision_stack[target_level + 1].trail_start
235 } else {
236 self.trail.len()
238 };
239
240 while self.trail.len() > target_trail_pos {
242 if let Some(assignment) = self.trail.pop() {
243 self.var_to_trail.remove(&assignment.var);
244 }
245 }
246
247 self.undo_to_level(target_level)?;
249
250 self.decision_stack.truncate(target_level + 1);
252 self.current_level = target_level;
253
254 Ok(())
255 }
256
257 pub fn restart(&mut self) -> Result<(), String> {
259 self.stats.restarts += 1;
260 self.backtrack(0)
261 }
262
263 pub fn record_undo(&mut self, action: UndoAction) {
265 self.undo_stack.push(action);
266 }
267
268 fn undo_to_level(&mut self, _target_level: usize) -> Result<(), String> {
270 let mut undos_to_apply = Vec::new();
274
275 while let Some(undo) = self.undo_stack.pop() {
278 undos_to_apply.push(undo);
279
280 if undos_to_apply.len() > 100 {
282 break;
283 }
284 }
285
286 for undo in undos_to_apply {
288 self.apply_undo(undo)?;
289 self.stats.undos += 1;
290 }
291
292 Ok(())
293 }
294
295 fn apply_undo(&mut self, _action: UndoAction) -> Result<(), String> {
297 Ok(())
300 }
301
302 pub fn push_checkpoint(&mut self) {
304 self.checkpoints.push(Checkpoint {
305 level: self.current_level,
306 trail_size: self.trail.len(),
307 undo_size: self.undo_stack.len(),
308 });
309 }
310
311 pub fn pop_checkpoint(&mut self) -> Result<(), String> {
313 if let Some(checkpoint) = self.checkpoints.pop() {
314 self.backtrack(checkpoint.level)?;
315
316 self.undo_stack.truncate(checkpoint.undo_size);
318
319 Ok(())
320 } else {
321 Err("No checkpoint to pop".to_string())
322 }
323 }
324
325 pub fn current_level(&self) -> usize {
327 self.current_level
328 }
329
330 pub fn is_assigned(&self, var: TermId) -> bool {
332 self.var_to_trail.contains_key(&var)
333 }
334
335 pub fn get_assignment(&self, var: TermId) -> Option<&Assignment> {
337 if let Some(&trail_idx) = self.var_to_trail.get(&var) {
338 self.trail.get(trail_idx)
339 } else {
340 None
341 }
342 }
343
344 pub fn current_assignments(&self) -> &[Assignment] {
346 &self.trail
347 }
348
349 pub fn get_decision(&self, level: usize) -> Option<&Assignment> {
351 if level < self.decision_stack.len() {
352 self.decision_stack[level].decision.as_ref()
353 } else {
354 None
355 }
356 }
357
358 pub fn stats(&self) -> &BacktrackStats {
360 &self.stats
361 }
362
363 pub fn reset_stats(&mut self) {
365 self.stats = BacktrackStats::default();
366 }
367}
368
369impl Default for BacktrackManager {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 #[test]
380 fn test_backtrack_manager() {
381 let mgr = BacktrackManager::new();
382 assert_eq!(mgr.current_level(), 0);
383 assert_eq!(mgr.stats.decisions, 0);
384 }
385
386 #[test]
387 fn test_decide() {
388 let mut mgr = BacktrackManager::new();
389
390 let var = TermId::from(1);
391 mgr.decide(var, true)
392 .expect("test operation should succeed");
393
394 assert_eq!(mgr.current_level(), 1);
395 assert_eq!(mgr.stats.decisions, 1);
396 assert!(mgr.is_assigned(var));
397 }
398
399 #[test]
400 fn test_propagate() {
401 let mut mgr = BacktrackManager::new();
402
403 let var1 = TermId::from(1);
404 mgr.decide(var1, true)
405 .expect("test operation should succeed");
406
407 let var2 = TermId::from(2);
408 let reason = ReasonClause::BooleanClause(vec![var1]);
409 mgr.propagate(var2, false, reason)
410 .expect("test operation should succeed");
411
412 assert_eq!(mgr.stats.propagations, 1);
413 assert!(mgr.is_assigned(var2));
414 }
415
416 #[test]
417 fn test_backtrack() {
418 let mut mgr = BacktrackManager::new();
419
420 let var1 = TermId::from(1);
421 mgr.decide(var1, true)
422 .expect("test operation should succeed");
423
424 let var2 = TermId::from(2);
425 mgr.decide(var2, false)
426 .expect("test operation should succeed");
427
428 assert_eq!(mgr.current_level(), 2);
429
430 mgr.backtrack(1).expect("test operation should succeed");
431
432 assert_eq!(mgr.current_level(), 1);
433 assert!(mgr.is_assigned(var1));
434 assert!(!mgr.is_assigned(var2));
435 }
436
437 #[test]
438 fn test_restart() {
439 let mut mgr = BacktrackManager::new();
440
441 mgr.decide(TermId::from(1), true)
442 .expect("test operation should succeed");
443 mgr.decide(TermId::from(2), false)
444 .expect("test operation should succeed");
445
446 mgr.restart().expect("test operation should succeed");
447
448 assert_eq!(mgr.current_level(), 0);
449 assert_eq!(mgr.stats.restarts, 1);
450 assert_eq!(mgr.trail.len(), 0);
451 }
452
453 #[test]
454 fn test_checkpoint() {
455 let mut mgr = BacktrackManager::new();
456
457 mgr.decide(TermId::from(1), true)
458 .expect("test operation should succeed");
459 mgr.push_checkpoint();
460
461 mgr.decide(TermId::from(2), false)
462 .expect("test operation should succeed");
463
464 mgr.pop_checkpoint().expect("test operation should succeed");
465
466 assert_eq!(mgr.current_level(), 1);
467 assert!(!mgr.is_assigned(TermId::from(2)));
468 }
469
470 #[test]
471 fn test_get_assignment() {
472 let mut mgr = BacktrackManager::new();
473
474 let var = TermId::from(1);
475 mgr.decide(var, true)
476 .expect("test operation should succeed");
477
478 let assignment = mgr
479 .get_assignment(var)
480 .expect("test operation should succeed");
481 assert!(assignment.value);
482 assert_eq!(assignment.level, 1);
483 assert!(assignment.reason.is_none());
484 }
485}