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