Skip to main content

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}