1use crate::adjacency::Adjacency;
28use crate::constraint::{ConstraintEnum, VarId};
29use crate::domain::Domain;
30use crate::ordering::{self, Ordering};
31use crate::solver::optimize::DomainCostEval;
32use crate::solver::{Solution, Trail, ac3, propagate};
33use crate::variable::Variable;
34use crate::{Pruning, SolveStats};
35
36pub const PERMANENT_DEPTH: usize = 0;
42const SEARCH_ROOT_DEPTH: usize = 1;
44
45#[derive(Debug, Clone)]
51pub struct SearchParams {
52 pub pruning: Pruning,
53 pub ordering: Ordering,
54 pub max_solutions: usize,
55 pub node_budget: Option<u64>,
58 pub cancel: Option<crate::CancelToken>,
61}
62
63enum Step {
67 Continue,
68 Done,
69}
70
71pub(crate) struct Kernel<'a, D: Domain> {
76 variables: &'a mut [Variable<D>],
77 constraints: &'a [ConstraintEnum<D>],
78 adjacency: &'a Adjacency,
79 weights: &'a mut [f64],
80 var_cids: &'a [Vec<usize>],
81 stats: &'a mut SolveStats,
82 trail: Trail,
83 worklist: ac3::BitsetWorklist,
89 params: &'a SearchParams,
90}
91
92impl<D: Domain> Kernel<'_, D>
93where
94 D::Value: PartialEq + 'static,
95{
96 #[inline]
98 fn is_valid(&self, var: VarId, assignment: &[Option<D::Value>]) -> bool {
99 for &ci in self.adjacency.constraints_for(var) {
100 let ci = ci as usize;
101 let scope = self.constraints[ci].scope();
102 if scope.iter().all(|&v| assignment[v as usize].is_some())
103 && !self.constraints[ci].check(assignment)
104 {
105 return false;
106 }
107 }
108 true
109 }
110
111 #[inline]
118 fn propagate_from(
119 &mut self,
120 var: VarId,
121 assignment: &mut Vec<Option<D::Value>>,
122 depth: usize,
123 ) -> bool {
124 match self.params.pruning {
125 Pruning::None => false,
126 Pruning::ForwardChecking => propagate::forward_check(
127 var,
128 self.variables,
129 self.constraints,
130 self.adjacency,
131 assignment.as_mut_slice(),
132 self.stats,
133 &mut self.trail,
134 depth,
135 )
136 .is_some(),
137 Pruning::Ac3 => ac3::ac3_from_variable(
138 var,
139 self.variables,
140 self.constraints,
141 self.adjacency,
142 assignment,
143 self.stats,
144 &mut self.trail,
145 &mut self.worklist,
146 depth,
147 )
148 .is_some(),
149 Pruning::AcFc => propagate::ac_fc(
150 var,
151 self.variables,
152 self.constraints,
153 self.adjacency,
154 assignment.as_mut_slice(),
155 self.stats,
156 &mut self.trail,
157 depth,
158 )
159 .is_some(),
160 }
161 }
162}
163
164trait SearchPolicy<D: Domain> {
167 fn on_leaf(&mut self, k: &mut Kernel<'_, D>, assignment: &[Option<D::Value>]) -> Step;
169
170 #[inline]
172 fn node_prune(&mut self, _k: &Kernel<'_, D>, _assignment: &[Option<D::Value>]) -> bool {
173 false
174 }
175
176 #[inline]
178 fn order_values(&self, _k: &Kernel<'_, D>, _var: VarId, _values: &mut [D::Value]) {}
179}
180
181fn search<D, P>(
183 k: &mut Kernel<'_, D>,
184 p: &mut P,
185 assignment: &mut Vec<Option<D::Value>>,
186 stack: &mut Vec<VarId>,
187 depth: usize,
188) -> Step
189where
190 D: Domain,
191 D::Value: PartialEq + 'static,
192 P: SearchPolicy<D>,
193{
194 if stack.is_empty() {
195 return p.on_leaf(k, assignment);
196 }
197
198 if let Some(budget) = k.params.node_budget
201 && k.stats.nodes_explored >= budget
202 {
203 k.stats.budget_exceeded = true;
204 return Step::Done;
205 }
206
207 if let Some(tok) = &k.params.cancel
213 && tok.is_cancelled()
214 {
215 k.stats.cancelled = true;
216 return Step::Done;
217 }
218
219 k.stats.nodes_explored += 1;
220
221 if p.node_prune(k, assignment) {
222 return Step::Continue;
223 }
224
225 let idx =
226 ordering::select_variable(stack, k.variables, k.params.ordering, k.weights, k.var_cids)
227 .unwrap();
228 let var = stack.swap_remove(idx);
229
230 let mut values: Vec<_> = k.variables[var as usize].domain.iter().collect();
231 p.order_values(k, var, &mut values);
232
233 for val in values {
234 let mark = k.trail.checkpoint();
235 assignment[var as usize] = Some(val.clone());
236
237 k.variables[var as usize].restrict_to(&val, depth);
239 k.trail.push(var);
240
241 if k.is_valid(var, assignment)
242 && !k.propagate_from(var, assignment, depth)
243 && let Step::Done = search(k, p, assignment, stack, depth + 1)
244 {
245 return Step::Done;
246 }
247
248 k.stats.backtracks += 1;
249 assignment[var as usize] = None;
250 k.trail.undo_to(mark, depth, k.variables);
251 }
252
253 stack.push(var);
254 Step::Continue
255}
256
257struct Feasibility<D: Domain> {
262 solutions: Vec<Solution<D>>,
263 max_solutions: usize,
264}
265
266impl<D: Domain> SearchPolicy<D> for Feasibility<D>
267where
268 D::Value: PartialEq + 'static,
269{
270 #[inline]
271 fn on_leaf(&mut self, _k: &mut Kernel<'_, D>, assignment: &[Option<D::Value>]) -> Step {
272 self.solutions.push(
273 assignment
274 .iter()
275 .map(|v| v.as_ref().unwrap().clone())
276 .collect(),
277 );
278 if self.solutions.len() >= self.max_solutions {
279 Step::Done
280 } else {
281 Step::Continue
282 }
283 }
284}
285
286#[allow(clippy::too_many_arguments)]
289pub fn feasibility_search<D: Domain>(
290 variables: &mut [Variable<D>],
291 constraints: &[ConstraintEnum<D>],
292 adjacency: &Adjacency,
293 weights: &mut [f64],
294 var_cids: &[Vec<usize>],
295 params: &SearchParams,
296 stats: &mut SolveStats,
297 given: Option<&[(VarId, D::Value)]>,
298) -> Vec<Solution<D>>
299where
300 D::Value: PartialEq + 'static,
301{
302 let num_vars = variables.len();
303 let mut assignment: Vec<Option<D::Value>> = vec![None; num_vars];
304
305 let mut stack: Vec<VarId> = if let Some(given) = given {
306 for (var, val) in given {
307 assignment[*var as usize] = Some(val.clone());
308 }
309 (0..num_vars as u32)
310 .filter(|v| assignment[*v as usize].is_none())
311 .collect()
312 } else {
313 (0..num_vars as u32).collect()
314 };
315
316 let mut policy = Feasibility {
317 solutions: Vec::new(),
318 max_solutions: params.max_solutions,
319 };
320 let mut kernel = Kernel {
321 variables,
322 constraints,
323 adjacency,
324 weights,
325 var_cids,
326 stats,
327 trail: Trail::default(),
328 worklist: ac3::BitsetWorklist::new(constraints.len()),
329 params,
330 };
331
332 search(
333 &mut kernel,
334 &mut policy,
335 &mut assignment,
336 &mut stack,
337 SEARCH_ROOT_DEPTH,
338 );
339
340 policy.solutions
341}
342
343struct ScoredSolution<D: Domain> {
348 solution: Solution<D>,
349 cost: f64,
350}
351
352struct BranchBound<'e, D: Domain> {
353 scored: Vec<ScoredSolution<D>>,
354 best_cost: f64,
355 maximize: bool,
356 eval: &'e dyn DomainCostEval<D>,
357}
358
359impl<D: Domain> BranchBound<'_, D>
360where
361 D::Value: PartialEq + 'static,
362{
363 fn assignment_cost(&self, k: &Kernel<'_, D>, assignment: &[Option<D::Value>]) -> f64 {
365 let mut cost = 0.0;
366 for (i, val) in assignment.iter().enumerate() {
367 if let Some(v) = val {
368 cost += self.eval.cost(&k.variables[i].domain, v);
369 }
370 }
371 cost
372 }
373
374 fn optimistic_bound(&self, k: &Kernel<'_, D>, assignment: &[Option<D::Value>]) -> f64 {
377 let mut bound = 0.0;
378 for (i, val) in assignment.iter().enumerate() {
379 match val {
380 Some(v) => bound += self.eval.cost(&k.variables[i].domain, v),
381 None if self.maximize => bound += self.eval.max_cost(&k.variables[i].domain),
382 None => bound += self.eval.min_cost(&k.variables[i].domain),
383 }
384 }
385 bound
386 }
387}
388
389impl<D: Domain> SearchPolicy<D> for BranchBound<'_, D>
390where
391 D::Value: PartialEq + 'static,
392{
393 #[inline]
394 fn on_leaf(&mut self, k: &mut Kernel<'_, D>, assignment: &[Option<D::Value>]) -> Step {
395 let cost = self.assignment_cost(k, assignment);
396 let effective = if self.maximize { -cost } else { cost };
397 if effective < self.best_cost {
398 self.best_cost = effective;
399 }
400 self.scored.push(ScoredSolution {
401 solution: assignment
402 .iter()
403 .map(|v| v.as_ref().unwrap().clone())
404 .collect(),
405 cost,
406 });
407 Step::Continue
409 }
410
411 #[inline]
412 fn node_prune(&mut self, k: &Kernel<'_, D>, assignment: &[Option<D::Value>]) -> bool {
413 let bound = self.optimistic_bound(k, assignment);
414 let effective = if self.maximize { -bound } else { bound };
415 effective >= self.best_cost
416 }
417
418 #[inline]
419 fn order_values(&self, k: &Kernel<'_, D>, var: VarId, values: &mut [D::Value]) {
420 let domain = &k.variables[var as usize].domain;
421 if self.maximize {
424 values.sort_by(|a, b| {
425 self.eval
426 .cost(domain, b)
427 .partial_cmp(&self.eval.cost(domain, a))
428 .unwrap_or(std::cmp::Ordering::Equal)
429 });
430 } else {
431 values.sort_by(|a, b| {
432 self.eval
433 .cost(domain, a)
434 .partial_cmp(&self.eval.cost(domain, b))
435 .unwrap_or(std::cmp::Ordering::Equal)
436 });
437 }
438 }
439}
440
441#[allow(clippy::too_many_arguments)]
444pub fn branch_and_bound<D: Domain>(
445 variables: &mut [Variable<D>],
446 constraints: &[ConstraintEnum<D>],
447 adjacency: &Adjacency,
448 weights: &mut [f64],
449 var_cids: &[Vec<usize>],
450 params: &SearchParams,
451 stats: &mut SolveStats,
452 maximize: bool,
453 cost_eval: &dyn DomainCostEval<D>,
454) -> Vec<Solution<D>>
455where
456 D::Value: PartialEq + 'static,
457{
458 let num_vars = variables.len();
459 let mut assignment: Vec<Option<D::Value>> = vec![None; num_vars];
460 let mut stack: Vec<VarId> = (0..num_vars as u32).collect();
461
462 let mut policy = BranchBound {
463 scored: Vec::new(),
464 best_cost: f64::INFINITY,
465 maximize,
466 eval: cost_eval,
467 };
468 let mut kernel = Kernel {
469 variables,
470 constraints,
471 adjacency,
472 weights,
473 var_cids,
474 stats,
475 trail: Trail::default(),
476 worklist: ac3::BitsetWorklist::new(constraints.len()),
477 params,
478 };
479
480 search(
481 &mut kernel,
482 &mut policy,
483 &mut assignment,
484 &mut stack,
485 SEARCH_ROOT_DEPTH,
486 );
487
488 if maximize {
490 policy.scored.sort_by(|a, b| {
491 b.cost
492 .partial_cmp(&a.cost)
493 .unwrap_or(std::cmp::Ordering::Equal)
494 });
495 } else {
496 policy.scored.sort_by(|a, b| {
497 a.cost
498 .partial_cmp(&b.cost)
499 .unwrap_or(std::cmp::Ordering::Equal)
500 });
501 }
502 policy.scored.truncate(params.max_solutions);
503 policy.scored.into_iter().map(|s| s.solution).collect()
504}