csp_solver/solver/
backjump.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
12use super::backtrack::Solution;
13
14pub fn backjump_search<D: Domain>(
16 variables: &mut [Variable<D>],
17 constraints: &[crate::constraint::ConstraintEnum<D>],
18 adjacency: &crate::adjacency::Adjacency,
19 config: &BackjumpConfig,
20 stats: &mut crate::SolveStats,
21) -> Vec<Solution<D>>
22where
23 D::Value: PartialEq,
24{
25 let num_vars = variables.len();
26 let mut assignment: Vec<Option<D::Value>> = vec![None; num_vars];
27 let mut stack: Vec<VarId> = (0..num_vars as u32).collect();
28 let mut solutions = Vec::new();
29 let mut ctx = SearchContext { variables, constraints, adjacency, stats };
30 let mut conflict = ConflictState {
31 assigned_order: Vec::new(),
32 membership: vec![false; num_vars],
33 };
34
35 backjump_recurse(
36 &mut ctx, config,
37 &mut assignment, &mut stack, &mut solutions,
38 &mut conflict, 0,
39 );
40
41 solutions
42}
43
44enum BackjumpResult {
45 Continue,
46 Done,
47 JumpTo(usize),
48}
49
50pub struct BackjumpConfig {
52 pub pruning: Pruning,
53 pub ordering: Ordering,
54 pub max_solutions: usize,
55 pub constraint_weights: Vec<f64>,
56 pub var_constraint_ids: Vec<Vec<usize>>,
57 pub node_budget: Option<u64>,
60}
61
62struct ConflictState {
65 assigned_order: Vec<VarId>,
67 membership: Vec<bool>,
69}
70
71fn backjump_recurse<D: Domain>(
72 ctx: &mut SearchContext<'_, D>,
73 config: &BackjumpConfig,
74 assignment: &mut Vec<Option<D::Value>>,
75 stack: &mut Vec<VarId>,
76 solutions: &mut Vec<Solution<D>>,
77 conflict: &mut ConflictState,
78 depth: usize,
79) -> BackjumpResult
80where
81 D::Value: PartialEq,
82{
83 if stack.is_empty() {
84 let sol: Solution<D> = assignment
85 .iter()
86 .map(|v| v.as_ref().unwrap().clone())
87 .collect();
88 solutions.push(sol);
89 if solutions.len() >= config.max_solutions {
90 return BackjumpResult::Done;
91 }
92 return BackjumpResult::Continue;
93 }
94
95 if let Some(budget) = config.node_budget
97 && ctx.stats.nodes_explored >= budget
98 {
99 ctx.stats.budget_exceeded = true;
100 return BackjumpResult::Done;
101 }
102
103 ctx.stats.nodes_explored += 1;
104
105 let idx = ordering::select_variable(
106 stack, ctx.variables, config.ordering,
107 &config.constraint_weights, &config.var_constraint_ids,
108 )
109 .unwrap();
110
111 let var = stack.swap_remove(idx);
112 conflict.assigned_order.push(var);
113
114 let mut conflict_vars: Vec<VarId> = Vec::new();
116
117 let values: Vec<_> = ctx.variables[var as usize].domain.iter().collect();
118 let mut exhausted = true;
119
120 for val in values {
121 assignment[var as usize] = Some(val.clone());
122
123 ctx.variables[var as usize].restrict_to(&val, depth);
125
126 let mut valid = true;
127 for &ci in ctx.adjacency.constraints_for(var) {
128 let ci = ci as usize;
129 let scope = ctx.constraints[ci].scope();
130 if scope.iter().all(|&v| assignment[v as usize].is_some())
131 && !ctx.constraints[ci].check(assignment)
132 {
133 valid = false;
134 for &v in scope {
135 if v != var
136 && assignment[v as usize].is_some()
137 && !conflict.membership[v as usize]
138 {
139 conflict.membership[v as usize] = true;
140 conflict_vars.push(v);
141 }
142 }
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 for &neighbor in ctx.adjacency.neighbors_of_var(var) {
166 if assignment[neighbor as usize].is_some()
167 && !conflict.membership[neighbor as usize]
168 {
169 conflict.membership[neighbor as usize] = true;
170 conflict_vars.push(neighbor);
171 }
172 }
173 } else {
174 match backjump_recurse(
175 ctx, config,
176 assignment, stack, solutions,
177 conflict, depth + 1,
178 ) {
179 BackjumpResult::Done => return BackjumpResult::Done,
180 BackjumpResult::Continue => {
181 exhausted = false;
182 }
183 BackjumpResult::JumpTo(target_depth) => {
184 ctx.stats.backtracks += 1;
185 assignment[var as usize] = None;
186 for v in ctx.variables.iter_mut() {
187 v.restore(depth);
188 }
189 conflict.assigned_order.pop();
190 stack.push(var);
191
192 if target_depth < depth {
193 for &cv in &conflict_vars {
194 conflict.membership[cv as usize] = false;
195 }
196 return BackjumpResult::JumpTo(target_depth);
197 }
198 continue;
199 }
200 }
201 }
202 }
203
204 ctx.stats.backtracks += 1;
205 assignment[var as usize] = None;
206 for v in ctx.variables.iter_mut() {
207 v.restore(depth);
208 }
209 }
210
211 conflict.assigned_order.pop();
212 stack.push(var);
213
214 let result = if exhausted && !conflict_vars.is_empty() {
215 let mut max_depth = 0;
217 let mut found = false;
218 for (pos, &av) in conflict.assigned_order.iter().enumerate() {
219 if conflict.membership[av as usize] && pos >= max_depth {
220 max_depth = pos;
221 found = true;
222 }
223 }
224 if found && max_depth < depth {
225 BackjumpResult::JumpTo(max_depth)
226 } else {
227 BackjumpResult::Continue
228 }
229 } else {
230 BackjumpResult::Continue
231 };
232
233 for &cv in &conflict_vars {
234 conflict.membership[cv as usize] = false;
235 }
236
237 result
238}