Skip to main content

csp_solver/
ordering.rs

1//! Variable ordering heuristics.
2
3use crate::constraint::VarId;
4use crate::domain::Domain;
5use crate::variable::Variable;
6
7/// Variable ordering strategy for search.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Ordering {
10    /// Chronological: pick variables in the order they appear on the stack.
11    Chronological,
12    /// Fail-first (MRV): pick the variable with the smallest domain.
13    FailFirst,
14    /// Mrv: pick the variable minimizing `domain-size / Σ constraint-weights`.
15    /// The weights are frozen at 1.0 (no dom/wdeg bumping is wired to the
16    /// kernel), so this is a static heuristic; `Chs` shares the same scan with
17    /// dynamically evolving weights.
18    Mrv,
19    /// CHS (Conflict-History Search): same `dom / Σ weights` scan as `Mrv`,
20    /// but the weights are updated dynamically by an exponential recency-weighted
21    /// average on each conflict (see [`crate::solver::heuristic`]).
22    Chs,
23}
24
25/// Select the next unassigned variable from the stack according to the ordering heuristic.
26///
27/// Returns the index into `stack` of the chosen variable, or `None` if empty.
28pub fn select_variable<D: Domain>(
29    stack: &[VarId],
30    variables: &[Variable<D>],
31    ordering: Ordering,
32    constraint_weights: &[f64],
33    var_constraint_ids: &[Vec<usize>],
34) -> Option<usize> {
35    if stack.is_empty() {
36        return None;
37    }
38
39    match ordering {
40        Ordering::Chronological => Some(stack.len() - 1),
41
42        Ordering::FailFirst => {
43            let mut best_idx = 0;
44            let mut best_size = usize::MAX;
45            for (i, &var) in stack.iter().enumerate() {
46                let sz = variables[var as usize].domain.size();
47                if sz < best_size {
48                    best_size = sz;
49                    best_idx = i;
50                }
51            }
52            Some(best_idx)
53        }
54
55        // CHS and Mrv share the `dom / Σ weights` branching rule; they
56        // differ only in *how* `constraint_weights` evolves (static 1.0 for
57        // Mrv, ERWA-updated for CHS — see `solver::heuristic`).
58        Ordering::Mrv | Ordering::Chs => {
59            let mut best_idx = 0;
60            let mut best_score = f64::MAX;
61            for (i, &var) in stack.iter().enumerate() {
62                let dom_size = variables[var as usize].domain.size() as f64;
63                let wdeg: f64 = var_constraint_ids[var as usize]
64                    .iter()
65                    .map(|&cid| constraint_weights[cid])
66                    .sum();
67                let score = dom_size / wdeg.max(1e-9);
68                if score < best_score {
69                    best_score = score;
70                    best_idx = i;
71                }
72            }
73            Some(best_idx)
74        }
75    }
76}