use crate::constraint::VarId;
use crate::domain::Domain;
use crate::ordering::{self, Ordering};
use crate::solver::ac3;
use crate::solver::propagate;
use crate::solver::SearchContext;
use crate::variable::Variable;
use crate::Pruning;
use super::backtrack::Solution;
pub fn backjump_search<D: Domain>(
variables: &mut [Variable<D>],
constraints: &[crate::constraint::ConstraintEnum<D>],
adjacency: &crate::adjacency::Adjacency,
config: &BackjumpConfig,
stats: &mut crate::SolveStats,
) -> Vec<Solution<D>>
where
D::Value: PartialEq,
{
let num_vars = variables.len();
let mut assignment: Vec<Option<D::Value>> = vec![None; num_vars];
let mut stack: Vec<VarId> = (0..num_vars as u32).collect();
let mut solutions = Vec::new();
let mut ctx = SearchContext { variables, constraints, adjacency, stats };
let mut conflict = ConflictState {
assigned_order: Vec::new(),
membership: vec![false; num_vars],
};
backjump_recurse(
&mut ctx, config,
&mut assignment, &mut stack, &mut solutions,
&mut conflict, 0,
);
solutions
}
enum BackjumpResult {
Continue,
Done,
JumpTo(usize),
}
pub struct BackjumpConfig {
pub pruning: Pruning,
pub ordering: Ordering,
pub max_solutions: usize,
pub constraint_weights: Vec<f64>,
pub var_constraint_ids: Vec<Vec<usize>>,
pub node_budget: Option<u64>,
}
struct ConflictState {
assigned_order: Vec<VarId>,
membership: Vec<bool>,
}
fn backjump_recurse<D: Domain>(
ctx: &mut SearchContext<'_, D>,
config: &BackjumpConfig,
assignment: &mut Vec<Option<D::Value>>,
stack: &mut Vec<VarId>,
solutions: &mut Vec<Solution<D>>,
conflict: &mut ConflictState,
depth: usize,
) -> BackjumpResult
where
D::Value: PartialEq,
{
if stack.is_empty() {
let sol: Solution<D> = assignment
.iter()
.map(|v| v.as_ref().unwrap().clone())
.collect();
solutions.push(sol);
if solutions.len() >= config.max_solutions {
return BackjumpResult::Done;
}
return BackjumpResult::Continue;
}
if let Some(budget) = config.node_budget
&& ctx.stats.nodes_explored >= budget
{
ctx.stats.budget_exceeded = true;
return BackjumpResult::Done;
}
ctx.stats.nodes_explored += 1;
let idx = ordering::select_variable(
stack, ctx.variables, config.ordering,
&config.constraint_weights, &config.var_constraint_ids,
)
.unwrap();
let var = stack.swap_remove(idx);
conflict.assigned_order.push(var);
let mut conflict_vars: Vec<VarId> = Vec::new();
let values: Vec<_> = ctx.variables[var as usize].domain.iter().collect();
let mut exhausted = true;
for val in values {
assignment[var as usize] = Some(val.clone());
ctx.variables[var as usize].restrict_to(&val, depth);
let mut valid = true;
for &ci in ctx.adjacency.constraints_for(var) {
let ci = ci as usize;
let scope = ctx.constraints[ci].scope();
if scope.iter().all(|&v| assignment[v as usize].is_some())
&& !ctx.constraints[ci].check(assignment)
{
valid = false;
for &v in scope {
if v != var
&& assignment[v as usize].is_some()
&& !conflict.membership[v as usize]
{
conflict.membership[v as usize] = true;
conflict_vars.push(v);
}
}
break;
}
}
if valid {
let dwo = match config.pruning {
Pruning::None => false,
Pruning::ForwardChecking => propagate::forward_check(
var, ctx.variables, ctx.constraints, ctx.adjacency,
assignment.as_mut_slice(), ctx.stats, depth,
),
Pruning::Ac3 => ac3::ac3_from_variable(
var, ctx.variables, ctx.constraints, ctx.adjacency,
assignment, ctx.stats, depth,
),
Pruning::AcFc => propagate::ac_fc(
var, ctx.variables, ctx.constraints, ctx.adjacency,
assignment.as_mut_slice(), ctx.stats, depth,
),
};
if dwo {
for &neighbor in ctx.adjacency.neighbors_of_var(var) {
if assignment[neighbor as usize].is_some()
&& !conflict.membership[neighbor as usize]
{
conflict.membership[neighbor as usize] = true;
conflict_vars.push(neighbor);
}
}
} else {
match backjump_recurse(
ctx, config,
assignment, stack, solutions,
conflict, depth + 1,
) {
BackjumpResult::Done => return BackjumpResult::Done,
BackjumpResult::Continue => {
exhausted = false;
}
BackjumpResult::JumpTo(target_depth) => {
ctx.stats.backtracks += 1;
assignment[var as usize] = None;
for v in ctx.variables.iter_mut() {
v.restore(depth);
}
conflict.assigned_order.pop();
stack.push(var);
if target_depth < depth {
for &cv in &conflict_vars {
conflict.membership[cv as usize] = false;
}
return BackjumpResult::JumpTo(target_depth);
}
continue;
}
}
}
}
ctx.stats.backtracks += 1;
assignment[var as usize] = None;
for v in ctx.variables.iter_mut() {
v.restore(depth);
}
}
conflict.assigned_order.pop();
stack.push(var);
let result = if exhausted && !conflict_vars.is_empty() {
let mut max_depth = 0;
let mut found = false;
for (pos, &av) in conflict.assigned_order.iter().enumerate() {
if conflict.membership[av as usize] && pos >= max_depth {
max_depth = pos;
found = true;
}
}
if found && max_depth < depth {
BackjumpResult::JumpTo(max_depth)
} else {
BackjumpResult::Continue
}
} else {
BackjumpResult::Continue
};
for &cv in &conflict_vars {
conflict.membership[cv as usize] = false;
}
result
}