use csp_solver::builder::assignment;
use csp_solver::constraint::{AllDifferent, LambdaConstraint, NotEqual, VarId};
use csp_solver::domain::bitset::BitsetDomain;
use csp_solver::ordering::Ordering;
use csp_solver::puzzles::futoshiki::{FutoshikiPuzzle, create_futoshiki_csp};
use csp_solver::{Csp, Pruning, SolveConfig};
fn fnv(sols: &[Vec<u32>]) -> u64 {
let mut h: u64 = 0xcbf29ce484222325;
for sol in sols {
for &v in sol {
for b in v.to_le_bytes() {
h ^= b as u64;
h = h.wrapping_mul(0x100000001b3);
}
}
h ^= 0xff; h = h.wrapping_mul(0x100000001b3);
}
h
}
fn set_hash(sols: &[Vec<u32>]) -> u64 {
let mut acc: u64 = 0;
for sol in sols {
acc ^= fnv(std::slice::from_ref(sol));
}
acc
}
const PRUNINGS: [Pruning; 4] = [
Pruning::None,
Pruning::ForwardChecking,
Pruning::Ac3,
Pruning::AcFc,
];
const ORDERINGS: [Ordering; 3] = [Ordering::Chronological, Ordering::FailFirst, Ordering::Mrv];
fn cfg(p: Pruning, o: Ordering, maxsol: usize) -> SolveConfig {
SolveConfig {
pruning: p,
ordering: o,
max_solutions: maxsol,
node_budget: Some(1_000_000),
..Default::default()
}
}
fn emit(
name: &str,
p: Pruning,
o: Ordering,
maxsol: usize,
sols: &[Vec<u32>],
st: &csp_solver::SolveStats,
) {
let tag = if maxsol == usize::MAX {
"all".to_string()
} else {
maxsol.to_string()
};
println!(
"P={name:<18} pr={p:?} or={o:?} max={tag} nodes={} bt={} prop={} budget={} nsol={} solhash={:#018x} sethash={:#018x}",
st.nodes_explored,
st.backtracks,
st.propagations,
st.budget_exceeded,
sols.len(),
fnv(sols),
set_hash(sols),
);
}
fn run<F: Fn() -> Csp<BitsetDomain>>(name: &str, build: F, maxsols: &[usize]) {
for &maxsol in maxsols {
for p in PRUNINGS {
for o in ORDERINGS {
let mut csp = build();
let sols = csp.solve(&cfg(p, o, maxsol));
emit(name, p, o, maxsol, &sols, csp.stats());
}
}
}
}
fn build_nqueens(n: u32) -> Csp<BitsetDomain> {
let mut csp = Csp::new();
let domain = BitsetDomain::new(0..n);
let vars = csp.add_variables(&domain, n as usize);
csp.add_constraint(AllDifferent::new(vars.clone()));
for i in 0..n {
for j in (i + 1)..n {
let vi = vars[i as usize];
let vj = vars[j as usize];
let diff = j - i;
csp.add_constraint(LambdaConstraint::new(
vec![vi, vj],
move |a: &[Option<u32>]| match (&a[vi as usize], &a[vj as usize]) {
(Some(ri), Some(rj)) => ri.abs_diff(*rj) != diff,
_ => true,
},
format!("diag({i},{j})"),
));
}
}
csp.finalize();
csp
}
fn build_australia() -> Csp<BitsetDomain> {
let mut csp = Csp::new();
let domain = BitsetDomain::new(0..3);
let v: Vec<VarId> = (0..7).map(|_| csp.add_variable(domain.clone())).collect();
let edges = [
(0, 1),
(0, 2),
(1, 2),
(1, 3),
(2, 3),
(2, 4),
(2, 5),
(3, 4),
(4, 5),
];
for (a, b) in edges {
csp.add_constraint(NotEqual::new(v[a], v[b]));
}
csp.finalize();
csp
}
fn build_permutation(n: u32) -> Csp<BitsetDomain> {
let mut csp = Csp::new();
let domain = BitsetDomain::new(0..n);
let vars = csp.add_variables(&domain, n as usize);
csp.add_constraint(AllDifferent::new(vars));
csp.finalize();
csp
}
fn build_futoshiki(input: &str) -> Csp<BitsetDomain> {
create_futoshiki_csp(&FutoshikiPuzzle::parse(input))
}
fn run_given(name: &str, n: u32, given: &[(VarId, u32)], maxsol: usize) {
for p in PRUNINGS {
for o in ORDERINGS {
let mut csp = build_permutation(n);
let sols = csp.solve_with_given(&cfg(p, o, maxsol), given);
emit(name, p, o, maxsol, &sols, csp.stats());
}
}
}
fn run_assignment() {
let costs = |r: usize, c: usize| ((r * 7 + c * 3 + (r * c) % 5) % 11) as f64 + 1.0;
let sol = assignment()
.rows(5)
.cols(5)
.cost(costs)
.solve()
.expect("assignment solve");
let st = &sol.stats;
println!(
"P={:<18} COP nodes={} bt={} prop={} budget={} cost={:.4} assign={:?}",
"assignment5x5",
st.nodes_explored,
st.backtracks,
st.propagations,
st.budget_exceeded,
sol.cost,
sol.assign
);
let sol2 = assignment()
.rows(6)
.cols(6)
.cost(|r, c| ((r * 5 + c * 2) % 9) as f64 + 0.5)
.row_group(|r| (r % 2) as u8)
.col_group(|c| (c % 2) as u8)
.unmatch_penalty(20.0)
.solve()
.expect("assignment grouped solve");
let st2 = &sol2.stats;
println!(
"P={:<18} COP nodes={} bt={} prop={} budget={} cost={:.4} assign={:?}",
"assignment6x6grp",
st2.nodes_explored,
st2.backtracks,
st2.propagations,
st2.budget_exceeded,
sol2.cost,
sol2.assign
);
}
fn main() {
let both: &[usize] = &[1, usize::MAX];
run("queens6", || build_nqueens(6), both);
run("queens8", || build_nqueens(8), both);
run("australia3", build_australia, both);
run("perm5", || build_permutation(5), both);
run("perm6", || build_permutation(6), &[usize::MAX]);
run(
"futoshiki_constr",
|| build_futoshiki("4\n0 5\n1 3\n1\n2\n"),
both,
);
run(
"futoshiki_loose",
|| build_futoshiki("4\n\n\n1\n2\n"),
&[usize::MAX],
);
run_given("given_perm5", 5, &[(0, 2)], usize::MAX);
run_given("given_perm6", 6, &[(0, 0), (1, 3)], usize::MAX);
run_assignment();
}