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