use csp_solver::constraint::{
AllDifferentExcept, Constraint, ConstraintEnum, Revision, VarId,
};
use csp_solver::domain::BitsetDomain;
use csp_solver::domain::Domain;
use csp_solver::variable::Variable;
use csp_solver::{Csp, Pruning, SolveConfig};
#[test]
fn check_allows_multiple_sentinels() {
let c = AllDifferentExcept::<u32>::new(vec![0, 1, 2], 0);
let assignment: Vec<Option<u32>> = vec![Some(0), Some(0), Some(7)];
assert!(<AllDifferentExcept<u32> as Constraint<BitsetDomain>>::check(
&c,
&assignment
));
}
#[test]
fn check_rejects_duplicate_non_sentinel() {
let c = AllDifferentExcept::<u32>::new(vec![0, 1, 2], 0);
let assignment: Vec<Option<u32>> = vec![Some(3), Some(3), Some(0)];
assert!(!<AllDifferentExcept<u32> as Constraint<BitsetDomain>>::check(
&c,
&assignment
));
}
#[test]
fn check_mixed_sentinel_and_distinct() {
let c = AllDifferentExcept::<u32>::new(vec![0, 1, 2, 3], 0);
let assignment: Vec<Option<u32>> = vec![Some(0), Some(1), Some(2), Some(0)];
assert!(<AllDifferentExcept<u32> as Constraint<BitsetDomain>>::check(
&c,
&assignment
));
}
#[test]
fn revise_prunes_non_sentinel_singleton() {
let mut vars: Vec<Variable<BitsetDomain>> = vec![
Variable::new(BitsetDomain::new([2])),
Variable::new(BitsetDomain::range(4)),
Variable::new(BitsetDomain::range(4)),
];
let c = AllDifferentExcept::<u32>::new(vec![0, 1, 2], 0);
let rev = <AllDifferentExcept<u32> as Constraint<BitsetDomain>>::revise(
&c, &mut vars, 0,
);
assert_eq!(rev, Revision::Changed, "non-sentinel singleton should prune peers");
assert!(!vars[1].domain.contains(&2));
assert!(!vars[2].domain.contains(&2));
assert!(vars[1].domain.contains(&0), "sentinel 0 must remain in vars[1]");
assert!(vars[2].domain.contains(&0), "sentinel 0 must remain in vars[2]");
}
#[test]
fn revise_skips_sentinel_singleton() {
let mut vars: Vec<Variable<BitsetDomain>> = vec![
Variable::new(BitsetDomain::new([0])),
Variable::new(BitsetDomain::range(4)),
Variable::new(BitsetDomain::range(4)),
];
let c = AllDifferentExcept::<u32>::new(vec![0, 1, 2], 0);
let rev = <AllDifferentExcept<u32> as Constraint<BitsetDomain>>::revise(
&c, &mut vars, 0,
);
assert_eq!(
rev,
Revision::Unchanged,
"sentinel singleton must not prune peers"
);
for v in 0u32..4 {
assert!(vars[1].domain.contains(&v), "vars[1] lost value {v}");
assert!(vars[2].domain.contains(&v), "vars[2] lost value {v}");
}
}
#[test]
fn solve_end_to_end_permits_sentinel_repeats() {
let mut csp: Csp<BitsetDomain> = Csp::new();
let vars: Vec<VarId> = csp.add_variables(&BitsetDomain::range(3), 4);
csp.add_constraint_enum(ConstraintEnum::AllDifferentExcept(
AllDifferentExcept::new(vars.clone(), 0),
));
csp.finalize();
let config = SolveConfig {
pruning: Pruning::ForwardChecking,
max_solutions: 1,
..Default::default()
};
let solutions = csp.solve(&config);
assert!(
!solutions.is_empty(),
"AllDifferentExcept with sentinel 0 must admit a feasible solution \
for 4 vars over 0..3"
);
let sol = &solutions[0];
let assigned: Vec<u32> = vars.iter().map(|&v| sol[v as usize]).collect();
let non_sentinel: Vec<u32> = assigned.iter().copied().filter(|v| *v != 0).collect();
let mut sorted = non_sentinel.clone();
sorted.sort();
sorted.dedup();
assert_eq!(
sorted.len(),
non_sentinel.len(),
"non-sentinel values must be pairwise distinct, got {assigned:?}"
);
}