csp_solver/variable.rs
1//! CSP variable with domain tracking and pruned-value undo log.
2
3use crate::domain::Domain;
4
5/// A CSP variable holding its current domain and an undo log for backtracking.
6#[derive(Debug, Clone)]
7pub struct Variable<D: Domain> {
8 /// Current (working) domain -- mutated during search.
9 pub domain: D,
10 /// Original domain -- used for full reset.
11 original_domain: D,
12 /// Undo log: `(depth, removed_value)` pairs. During backtracking, all entries
13 /// at the given depth are restored into the domain.
14 pruned: Vec<(usize, D::Value)>,
15}
16
17impl<D: Domain> Variable<D> {
18 /// Create a new variable with the given initial domain.
19 pub fn new(domain: D) -> Self {
20 Self {
21 original_domain: domain.clone(),
22 domain,
23 pruned: Vec::new(),
24 }
25 }
26
27 /// Record that `val` was pruned at `depth`, and remove it from the domain.
28 /// Returns `true` if the value was actually present and removed.
29 pub fn prune(&mut self, val: &D::Value, depth: usize) -> bool {
30 if self.domain.remove(val) {
31 self.pruned.push((depth, val.clone()));
32 true
33 } else {
34 false
35 }
36 }
37
38 /// Restore all values pruned at the given `depth`.
39 pub fn restore(&mut self, depth: usize) {
40 while let Some(&(d, _)) = self.pruned.last() {
41 if d == depth {
42 let (_, val) = self.pruned.pop().unwrap();
43 self.domain.add(&val);
44 } else {
45 break;
46 }
47 }
48 }
49
50 /// Reset to the original domain, clearing the undo log.
51 pub fn reset(&mut self) {
52 self.domain = self.original_domain.clone();
53 self.pruned.clear();
54 }
55
56 /// Replace the current domain entirely (used during initial propagation).
57 pub fn set_domain(&mut self, domain: D) {
58 self.domain = domain;
59 }
60
61 /// Restrict domain to a single value, recording all other removals at `depth`.
62 /// This is the fast path for backtracking assignment — avoids collecting
63 /// domain values into a Vec just to prune them.
64 pub fn restrict_to(&mut self, val: &D::Value, depth: usize) {
65 // Snapshot current values, then prune non-matching.
66 // For BitsetDomain (u128), values() is just bit iteration — cheap.
67 // The undo log records each removal for restore(depth).
68 let vals: Vec<_> = self.domain.iter().collect();
69 for v in &vals {
70 if v != val {
71 self.domain.remove(v);
72 self.pruned.push((depth, v.clone()));
73 }
74 }
75 }
76}