csp_solver/solver/
backtrack.rs1use crate::constraint::VarId;
4use crate::domain::Domain;
5use crate::ordering::{self, Ordering};
6use crate::solver::ac3;
7use crate::solver::propagate;
8use crate::solver::SearchContext;
9use crate::variable::Variable;
10use crate::Pruning;
11
12pub type Solution<D> = Vec<<D as Domain>::Value>;
14
15pub fn backtrack_search<D: Domain>(
17 variables: &mut [Variable<D>],
18 constraints: &[crate::constraint::ConstraintEnum<D>],
19 adjacency: &crate::adjacency::Adjacency,
20 config: &BacktrackConfig,
21 stats: &mut crate::SolveStats,
22) -> Vec<Solution<D>>
23where
24 D::Value: PartialEq,
25{
26 let num_vars = variables.len();
27 let mut assignment: Vec<Option<D::Value>> = vec![None; num_vars];
28 let mut stack: Vec<VarId> = (0..num_vars as u32).collect();
29 let mut solutions = Vec::new();
30 let mut ctx = SearchContext { variables, constraints, adjacency, stats };
31
32 backtrack_recurse(
33 &mut ctx, config,
34 &mut assignment, &mut stack, &mut solutions, 0,
35 );
36
37 solutions
38}
39
40pub fn backtrack_search_with_given<D: Domain>(
42 variables: &mut [Variable<D>],
43 constraints: &[crate::constraint::ConstraintEnum<D>],
44 adjacency: &crate::adjacency::Adjacency,
45 config: &BacktrackConfig,
46 stats: &mut crate::SolveStats,
47 given: &[(VarId, D::Value)],
48) -> Vec<Solution<D>>
49where
50 D::Value: PartialEq,
51{
52 let num_vars = variables.len();
53 let mut assignment: Vec<Option<D::Value>> = vec![None; num_vars];
54
55 for (var, val) in given {
56 assignment[*var as usize] = Some(val.clone());
57 }
58
59 let mut stack: Vec<VarId> = (0..num_vars as u32)
60 .filter(|v| assignment[*v as usize].is_none())
61 .collect();
62
63 let mut solutions = Vec::new();
64 let mut ctx = SearchContext { variables, constraints, adjacency, stats };
65
66 backtrack_recurse(
67 &mut ctx, config,
68 &mut assignment, &mut stack, &mut solutions, 0,
69 );
70
71 solutions
72}
73
74pub struct BacktrackConfig {
76 pub pruning: Pruning,
77 pub ordering: Ordering,
78 pub max_solutions: usize,
79 pub constraint_weights: Vec<f64>,
80 pub var_constraint_ids: Vec<Vec<usize>>,
81 pub node_budget: Option<u64>,
84}
85
86fn backtrack_recurse<D: Domain>(
87 ctx: &mut SearchContext<'_, D>,
88 config: &BacktrackConfig,
89 assignment: &mut Vec<Option<D::Value>>,
90 stack: &mut Vec<VarId>,
91 solutions: &mut Vec<Solution<D>>,
92 depth: usize,
93) -> bool
94where
95 D::Value: PartialEq,
96{
97 if stack.is_empty() {
98 let sol: Solution<D> = assignment
99 .iter()
100 .map(|v| v.as_ref().unwrap().clone())
101 .collect();
102 solutions.push(sol);
103 return solutions.len() >= config.max_solutions;
104 }
105
106 if let Some(budget) = config.node_budget
112 && ctx.stats.nodes_explored >= budget
113 {
114 ctx.stats.budget_exceeded = true;
115 return true;
116 }
117
118 ctx.stats.nodes_explored += 1;
119
120 let idx = ordering::select_variable(
121 stack, ctx.variables, config.ordering,
122 &config.constraint_weights, &config.var_constraint_ids,
123 )
124 .unwrap();
125
126 let var = stack.swap_remove(idx);
127 let values: Vec<_> = ctx.variables[var as usize].domain.iter().collect();
128
129 for val in values {
130 assignment[var as usize] = Some(val.clone());
131
132 ctx.variables[var as usize].restrict_to(&val, depth);
134
135 let mut valid = true;
136 for &ci in ctx.adjacency.constraints_for(var) {
137 let ci = ci as usize;
138 let scope = ctx.constraints[ci].scope();
139 if scope.iter().all(|&v| assignment[v as usize].is_some())
140 && !ctx.constraints[ci].check(assignment)
141 {
142 valid = false;
143 break;
144 }
145 }
146
147 if valid {
148 let dwo = match config.pruning {
149 Pruning::None => false,
150 Pruning::ForwardChecking => propagate::forward_check(
151 var, ctx.variables, ctx.constraints, ctx.adjacency,
152 assignment.as_mut_slice(), ctx.stats, depth,
153 ),
154 Pruning::Ac3 => ac3::ac3_from_variable(
155 var, ctx.variables, ctx.constraints, ctx.adjacency,
156 assignment, ctx.stats, depth,
157 ),
158 Pruning::AcFc => propagate::ac_fc(
159 var, ctx.variables, ctx.constraints, ctx.adjacency,
160 assignment.as_mut_slice(), ctx.stats, depth,
161 ),
162 };
163
164 if !dwo
165 && backtrack_recurse(
166 ctx, config,
167 assignment, stack, solutions, depth + 1,
168 )
169 {
170 return true;
171 }
172 }
173
174 ctx.stats.backtracks += 1;
175 assignment[var as usize] = None;
176 for v in ctx.variables.iter_mut() {
177 v.restore(depth);
178 }
179 }
180
181 stack.push(var);
182 false
183}