Skip to main content

csp_solver/csp/
mod.rs

1//! CSP builder surface — variable / constraint construction and `finalize()`.
2//!
3//! The [`Csp<D>`] container and its configuration vocabulary are defined in
4//! [`crate::config`]; the solve / propagate dispatch in [`solve`].
5//!
6//! Tests: `tests/solver.rs` (builder + solve correctness, every `add_*` path).
7
8pub mod solve;
9
10use crate::config::{Csp, SolveStats};
11use crate::constraint::{self, AllDifferent, Constraint, ConstraintEnum, NotEqual, VarId};
12use crate::domain::Domain;
13use crate::solver::adjacency::Adjacency;
14use crate::variable::Variable;
15
16impl<D: Domain> Csp<D> {
17    /// Create a new empty CSP.
18    pub fn new() -> Self {
19        Self {
20            variables: Vec::new(),
21            constraints: Vec::new(),
22            adjacency: None,
23            stats: SolveStats::default(),
24            constraint_weights: Vec::new(),
25            var_constraint_ids: Vec::new(),
26        }
27    }
28
29    /// Add a variable with the given domain. Returns its VarId.
30    pub fn add_variable(&mut self, domain: D) -> VarId {
31        let id = self.variables.len() as VarId;
32        self.variables.push(Variable::new(domain));
33        id
34    }
35
36    /// Add multiple variables sharing the same domain. Returns their VarIds.
37    pub fn add_variables(&mut self, domain: &D, count: usize) -> Vec<VarId> {
38        (0..count)
39            .map(|_| self.add_variable(domain.clone()))
40            .collect()
41    }
42
43    /// Add a custom constraint (wrapped in the `Custom` enum variant).
44    pub fn add_constraint(&mut self, c: impl Constraint<D> + 'static) {
45        self.constraints.push(ConstraintEnum::Custom(Box::new(c)));
46    }
47
48    /// Add a pre-typed constraint enum directly (avoids boxing for built-in types).
49    pub fn add_constraint_enum(&mut self, c: ConstraintEnum<D>) {
50        self.constraints.push(c);
51    }
52
53    /// Add a not-equal constraint (devirtualized fast path).
54    pub fn add_not_equal(&mut self, x: VarId, y: VarId) {
55        self.constraints
56            .push(ConstraintEnum::NotEqual(NotEqual::new(x, y)));
57    }
58
59    /// Add an all-different constraint (devirtualized fast path).
60    pub fn add_all_different(&mut self, vars: Vec<VarId>) {
61        self.constraints
62            .push(ConstraintEnum::AllDifferent(AllDifferent::new(vars)));
63    }
64
65    /// Fix a variable to a specific value.
66    pub fn add_equals(&mut self, var: VarId, value: D::Value)
67    where
68        D: 'static,
69        D::Value: Send + Sync,
70    {
71        self.add_constraint(constraint::LambdaConstraint::new(
72            vec![var],
73            move |assignment| match &assignment[var as usize] {
74                Some(v) => *v == value,
75                None => true,
76            },
77            format!("equals({var})"),
78        ));
79    }
80
81    /// Constrain x < y (for Ord-comparable values).
82    pub fn add_less_than(&mut self, x: VarId, y: VarId)
83    where
84        D: 'static,
85        D::Value: PartialOrd + Send + Sync,
86    {
87        self.add_constraint(constraint::LambdaConstraint::new(
88            vec![x, y],
89            move |assignment| match (&assignment[x as usize], &assignment[y as usize]) {
90                (Some(a), Some(b)) => a < b,
91                _ => true,
92            },
93            format!("less_than({x},{y})"),
94        ));
95    }
96
97    /// Constrain x > y (for Ord-comparable values).
98    pub fn add_greater_than(&mut self, x: VarId, y: VarId)
99    where
100        D: 'static,
101        D::Value: PartialOrd + Send + Sync,
102    {
103        self.add_constraint(constraint::LambdaConstraint::new(
104            vec![x, y],
105            move |assignment| match (&assignment[x as usize], &assignment[y as usize]) {
106                (Some(a), Some(b)) => a > b,
107                _ => true,
108            },
109            format!("greater_than({x},{y})"),
110        ));
111    }
112
113    /// Build the adjacency graph. Must be called after all variables and
114    /// constraints have been added, before calling `solve()`.
115    pub fn finalize(&mut self)
116    where
117        D::Value: PartialEq + 'static,
118    {
119        let num_vars = self.variables.len();
120        self.adjacency = Some(Adjacency::build(num_vars, &self.constraints));
121
122        self.constraint_weights = vec![1.0; self.constraints.len()];
123        self.var_constraint_ids = vec![Vec::new(); num_vars];
124        for (ci, c) in self.constraints.iter().enumerate() {
125            for &v in c.scope() {
126                self.var_constraint_ids[v as usize].push(ci);
127            }
128        }
129    }
130}