#![allow(missing_docs)]
use std::collections::HashMap;
use std::fmt;
use std::time::Instant;
#[derive(Clone, Debug)]
pub struct Variable {
pub name: String,
pub domain: Vec<i64>,
}
impl Variable {
pub fn new(name: &str, domain: Vec<i64>) -> Self {
Variable { name: name.to_string(), domain }
}
pub fn range(name: &str, lo: i64, hi: i64) -> Self {
Variable { name: name.to_string(), domain: (lo..=hi).collect() }
}
}
pub type UnaryCheck = fn(i64) -> bool;
pub type BinaryCheck = fn(i64, i64) -> bool;
pub type NaryCheck = fn(&[i64]) -> bool;
#[derive(Clone)]
pub enum Constraint {
Unary {
var: usize,
check: UnaryCheck,
desc: &'static str,
},
Binary {
a: usize,
b: usize,
check: BinaryCheck,
desc: &'static str,
},
Nary {
vars: Vec<usize>,
check: NaryCheck,
desc: &'static str,
},
}
impl fmt::Debug for Constraint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Constraint::Unary { var, desc, .. } => write!(f, "Unary({}, {})", var, desc),
Constraint::Binary { a, b, desc, .. } => write!(f, "Binary({}, {}, {})", a, b, desc),
Constraint::Nary { vars, desc, .. } => write!(f, "Nary({:?}, {})", vars, desc),
}
}
}
impl Constraint {
pub fn vars(&self) -> Vec<usize> {
match self {
Constraint::Unary { var, .. } => vec![*var],
Constraint::Binary { a, b, .. } => vec![*a, *b],
Constraint::Nary { vars, .. } => vars.clone(),
}
}
pub fn involves(&self, idx: usize) -> bool {
self.vars().contains(&idx)
}
}
#[derive(Clone)]
pub struct ConstraintProblem {
pub variables: Vec<Variable>,
pub constraints: Vec<Constraint>,
domain_index: HashMap<String, usize>,
}
impl ConstraintProblem {
pub fn new(variables: Vec<Variable>, constraints: Vec<Constraint>) -> Self {
let mut domain_index = HashMap::new();
for (i, v) in variables.iter().enumerate() {
domain_index.insert(v.name.clone(), i);
}
ConstraintProblem { variables, constraints, domain_index }
}
pub fn var_index(&self, name: &str) -> Option<usize> {
self.domain_index.get(name).copied()
}
pub fn is_consistent(&self, assignment: &[(usize, i64)]) -> bool {
let mut map: HashMap<usize, i64> = assignment.iter().copied().collect();
for c in &self.constraints {
match c {
Constraint::Unary { var, check, .. } => {
if let Some(&v) = map.get(var) {
if !check(v) {
return false;
}
}
}
Constraint::Binary { a, b, check, .. } => {
if let (Some(&va), Some(&vb)) = (map.get(a), map.get(b)) {
if !check(va, vb) {
return false;
}
}
}
Constraint::Nary { vars, check, .. } => {
let vals: Vec<i64> = vars.iter().filter_map(|v| map.get(v).copied()).collect();
if vals.len() == vars.len() && !check(&vals) {
return false;
}
}
}
}
true
}
pub fn is_satisfied(&self, assignment: &HashMap<usize, i64>) -> bool {
for c in &self.constraints {
match c {
Constraint::Unary { var, check, .. } => {
if !check(assignment[var]) { return false; }
}
Constraint::Binary { a, b, check, .. } => {
if !check(assignment[a], assignment[b]) { return false; }
}
Constraint::Nary { vars, check, .. } => {
let vals: Vec<i64> = vars.iter().map(|v| assignment[v]).collect();
if !check(&vals) { return false; }
}
}
}
true
}
pub fn var_count(&self) -> usize {
self.variables.len()
}
pub fn domain_size(&self, var: usize) -> usize {
self.variables[var].domain.len()
}
pub fn domain_values(&self, var: usize) -> &[i64] {
&self.variables[var].domain
}
pub fn constraints_involving(&self, var: usize) -> Vec<&Constraint> {
self.constraints.iter().filter(|c| c.involves(var)).collect()
}
pub fn all_diff(vars: &[usize]) -> Vec<Constraint> {
let mut cs = Vec::new();
for i in 0..vars.len() {
for j in (i + 1)..vars.len() {
let ai = vars[i];
let aj = vars[j];
cs.push(Constraint::Binary {
a: ai,
b: aj,
check: neq_fn,
desc: "alldiff",
});
}
}
cs
}
pub fn queen_diag(n: usize) -> Vec<Constraint> {
let mut cs = Vec::new();
for i in 0..n {
for j in (i + 1)..n {
let d = (i as i64 - j as i64).abs();
let vars = vec![i, j];
cs.push(Constraint::Nary {
vars,
check: queen_diag_fn,
desc: "qdiag",
});
}
}
cs
}
}
pub fn neq_fn(x: i64, y: i64) -> bool { x != y }
pub fn eq_fn(x: i64, y: i64) -> bool { x == y }
pub fn lt_fn(x: i64, y: i64) -> bool { x < y }
pub fn queen_diag_fn(vals: &[i64]) -> bool {
true }
#[derive(Clone, Debug)]
pub struct SolverConfig {
pub use_mrv: bool,
pub use_lcv: bool,
pub use_forward_checking: bool,
pub use_ac3: bool,
}
impl Default for SolverConfig {
fn default() -> Self {
SolverConfig {
use_mrv: true,
use_lcv: false,
use_forward_checking: true,
use_ac3: true,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct SolverStats {
pub nodes_visited: u64,
pub backtracks: u64,
pub propagations: u64,
pub elapsed: std::time::Duration,
}
impl SolverStats {
pub fn new() -> Self {
SolverStats::default()
}
pub fn summary(&self) -> String {
format!(
"nodes={} backtracks={} propagations={} time={:?}",
self.nodes_visited, self.backtracks, self.propagations, self.elapsed
)
}
}
pub fn neq(a: usize, b: usize) -> Constraint {
Constraint::Binary { a, b, check: neq_fn, desc: "!=" }
}
pub fn eq(a: usize, b: usize) -> Constraint {
Constraint::Binary { a, b, check: eq_fn, desc: "==" }
}
pub fn lt(a: usize, b: usize) -> Constraint {
Constraint::Binary { a, b, check: lt_fn, desc: "<" }
}