From f265a165dd81e8bda037807a5e0495618bc58295 Mon Sep 17 00:00:00 2001 From: David Wang Date: Sat, 11 Mar 2017 10:06:42 +1100 Subject: [PATCH] Add unification constraint. This constraint unifies two variables. This is done by substituting one variable for another in all of the current constraints, resulting a new set of constraints. We would like to make the equality constraint perform the more general substitution: "coef1 var1 -> coef2 var2 + constant". While this constraint is less general, it can be used when the variables are not integer quantities. --- src/constraint/mod.rs | 2 + src/constraint/unify.rs | 109 +++++++++++++++++++++++++++++++++++ src/puzzle.rs | 124 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 223 insertions(+), 12 deletions(-) create mode 100644 src/constraint/unify.rs diff --git a/src/constraint/mod.rs b/src/constraint/mod.rs index 27821ea..ab4ab17 100644 --- a/src/constraint/mod.rs +++ b/src/constraint/mod.rs @@ -35,6 +35,8 @@ pub trait Constraint { pub use self::alldifferent::AllDifferent; pub use self::equality::Equality; +pub use self::unify::Unify; mod alldifferent; mod equality; +mod unify; diff --git a/src/constraint/unify.rs b/src/constraint/unify.rs new file mode 100644 index 0000000..52ccfd7 --- /dev/null +++ b/src/constraint/unify.rs @@ -0,0 +1,109 @@ +//! Unify implementation. + +use std::iter; +use std::rc::Rc; + +use ::{Constraint,PsResult,PuzzleSearch,VarToken}; + +pub struct Unify { + var1: VarToken, + var2: VarToken, +} + +impl Unify { + /// Allocate a new Unify constraint. + /// + /// # Examples + /// + /// ``` + /// let mut send_more_money = puzzle_solver::Puzzle::new(); + /// let carry = send_more_money.new_vars_with_candidates_1d(4, &[0,1]); + /// let vars = send_more_money.new_vars_with_candidates_1d(8, + /// &[0,1,2,3,4,5,6,7,8,9]); + /// + /// let m = vars[4]; + /// puzzle_solver::constraint::Unify::new(m, carry[3]); + /// ``` + pub fn new(var1: VarToken, var2: VarToken) -> Self { + Unify { + var1: var1, + var2: var2, + } + } +} + +impl Constraint for Unify { + fn vars<'a>(&'a self) -> Box + 'a> { + if self.var1 != self.var2 { + Box::new(iter::once(&self.var1).chain(iter::once(&self.var2))) + } else { + Box::new(iter::empty()) + } + } + + fn on_updated(&self, search: &mut PuzzleSearch) -> PsResult<()> { + if self.var1 != self.var2 { + search.unify(self.var1, self.var2) + } else { + Ok(()) + } + } + + fn substitute(&self, from: VarToken, to: VarToken) + -> PsResult> { + let var1 = if self.var1 == from { to } else { self.var1 }; + let var2 = if self.var2 == from { to } else { self.var2 }; + Ok(Rc::new(Unify{ var1: var1, var2: var2 })) + } +} + +#[cfg(test)] +mod tests { + use ::Puzzle; + use super::Unify; + + #[test] + fn test_unify_alldifferent() { + let mut puzzle = Puzzle::new(); + let v0 = puzzle.new_var_with_candidates(&[1,2]); + let v1 = puzzle.new_var_with_candidates(&[1,2]); + + puzzle.all_different(&[v0, v1]); + puzzle.add_constraint(Unify::new(v0, v1)); + + let search = puzzle.step(); + assert!(search.is_none()); + } + + #[test] + fn test_unify_equality() { + let mut puzzle = Puzzle::new(); + let v0 = puzzle.new_var_with_candidates(&[1,2,3,4]); + let v1 = puzzle.new_var_with_candidates(&[1,2,3,4]); + let v2 = puzzle.new_var_with_candidates(&[1,2,3,4]); + + puzzle.equals(v0 + 2 * v1 + v2, 6); + puzzle.add_constraint(Unify::new(v0, v1)); + + let search = puzzle.step().expect("contradiction"); + assert_eq!(search[v0], 1); + assert_eq!(search[v1], 1); + assert_eq!(search[v2], 3); + } + + #[test] + fn test_unify_unify() { + let mut puzzle = Puzzle::new(); + let v0 = puzzle.new_var_with_candidates(&[1]); + let v1 = puzzle.new_var_with_candidates(&[1,2,3,4]); + let v2 = puzzle.new_var_with_candidates(&[1,2,3,4]); + + puzzle.add_constraint(Unify::new(v0, v1)); + puzzle.add_constraint(Unify::new(v1, v2)); + + let search = puzzle.step().expect("contradiction"); + assert_eq!(search[v0], 1); + assert_eq!(search[v1], 1); + assert_eq!(search[v2], 1); + } +} diff --git a/src/puzzle.rs b/src/puzzle.rs index 88967b7..afd8a6e 100644 --- a/src/puzzle.rs +++ b/src/puzzle.rs @@ -25,6 +25,7 @@ enum Candidates { enum VarState { Assigned(Val), Unassigned(Candidates), + Unified(VarToken), } /// The puzzle to be solved. @@ -442,6 +443,24 @@ impl PuzzleConstraints { } } + /// Create a new puzzle constraint container reflecting the + /// substitution of "from" with "to". + fn substitute(&self, from: VarToken, to: VarToken) -> PsResult { + let VarToken(idx) = from; + let mut new_constraints = self.constraints.clone(); + + for cidx in self.wake[idx].iter() { + let rc = try!(self.constraints[cidx].substitute(from, to)); + new_constraints[cidx] = rc; + } + + let wake = Self::init_wake(&new_constraints, self.wake.len()); + Ok(PuzzleConstraints { + constraints: new_constraints, + wake: wake, + }) + } + /// Determine which variables wake up which constraints. fn init_wake(constraints: &Vec>, num_vars: usize) -> Vec { @@ -484,6 +503,7 @@ impl<'a> PuzzleSearch<'a> { match &self.vars[idx] { &VarState::Assigned(_) => true, &VarState::Unassigned(_) => false, + &VarState::Unified(other) => self.is_assigned(other), } } @@ -496,6 +516,7 @@ impl<'a> PuzzleSearch<'a> { match &self.vars[idx] { &VarState::Assigned(val) => Some(val), &VarState::Unassigned(_) => None, + &VarState::Unified(other) => self.get_assigned(other), } } @@ -506,6 +527,7 @@ impl<'a> PuzzleSearch<'a> { match &self.vars[idx] { &VarState::Assigned(_) => Box::new(iter::empty()), &VarState::Unassigned(ref cs) => cs.iter(), + &VarState::Unified(other) => self.get_unassigned(other), } } @@ -523,6 +545,7 @@ impl<'a> PuzzleSearch<'a> { .ok_or(()) } }, + &VarState::Unified(other) => self.get_min_max(other), } } @@ -538,9 +561,12 @@ impl<'a> PuzzleSearch<'a> { &Candidates::Value(v) => return bool_to_result(v == val), &Candidates::Set(_) => (), }, + &VarState::Unified(_) => (), } - if let &mut VarState::Unassigned(Candidates::Set(ref mut rc)) + if let &VarState::Unified(other) = &self.vars[idx] { + self.set_candidate(other, val) + } else if let &mut VarState::Unassigned(Candidates::Set(ref mut rc)) = &mut self.vars[idx] { if rc.contains(&val) { let mut set = Rc::make_mut(rc); @@ -568,9 +594,12 @@ impl<'a> PuzzleSearch<'a> { &Candidates::Value(v) => return bool_to_result(v != val), &Candidates::Set(_) => (), }, + &VarState::Unified(_) => (), } - if let &mut VarState::Unassigned(Candidates::Set(ref mut rc)) + if let &VarState::Unified(other) = &self.vars[idx] { + self.remove_candidate(other, val) + } else if let &mut VarState::Unassigned(Candidates::Set(ref mut rc)) = &mut self.vars[idx] { if rc.contains(&val) { let mut set = Rc::make_mut(rc); @@ -605,9 +634,12 @@ impl<'a> PuzzleSearch<'a> { }, &Candidates::Set(_) => (), }, + &VarState::Unified(_) => (), } - if let &mut VarState::Unassigned(Candidates::Set(ref mut rc)) + if let &VarState::Unified(other) = &self.vars[idx] { + self.bound_candidate_range(other, min, max) + } else if let &mut VarState::Unassigned(Candidates::Set(ref mut rc)) = &mut self.vars[idx] { let &curr_min = rc.iter().min().expect("candidates"); let &curr_max = rc.iter().max().expect("candidates"); @@ -640,8 +672,8 @@ impl<'a> PuzzleSearch<'a> { let next_unassigned = self.vars.iter().enumerate().min_by_key( |&(_, vs)| match vs { - &VarState::Assigned(_) => ::std::usize::MAX, &VarState::Unassigned(ref cs) => cs.len(), + _ => ::std::usize::MAX, }); if let Some((idx, &VarState::Unassigned(ref cs))) = next_unassigned { @@ -667,13 +699,8 @@ impl<'a> PuzzleSearch<'a> { } } else { // No unassigned variables remaining. - let mut vars = Vec::with_capacity(self.puzzle.num_vars); - for var in self.vars.iter() { - match var { - &VarState::Assigned(val) => vars.push(val), - &VarState::Unassigned(_) => unreachable!(), - } - } + let vars = (0..self.puzzle.num_vars).map(|idx| + self[VarToken(idx)]).collect(); solutions.push(Solution{ vars: vars }); } } @@ -712,7 +739,8 @@ impl<'a> PuzzleSearch<'a> { 0 => return Err(()), 1 => cs.iter().next(), _ => None, - } + }, + &VarState::Unified(_) => None, }; if let Some(val) = gimme { @@ -737,6 +765,62 @@ impl<'a> PuzzleSearch<'a> { Ok(()) } + + /// Unify the "from" variable with the "to" variable. + pub fn unify(&mut self, from: VarToken, to: VarToken) + -> PsResult<()> { + if from == to { + return Ok(()); + } + + // Some preliminary checks, and maybe swap "from" and "to" in + // order to simplify our logic. + let (from, to) = { + let VarToken(search) = from; + let VarToken(replace) = to; + match (&self.vars[search], &self.vars[replace]) { + (&VarState::Assigned(val1), &VarState::Assigned(val2)) => + return bool_to_result(val1 == val2), + + // Unified variables should have been substituted out previously. + (&VarState::Unified(_), _) | (_, &VarState::Unified(_)) => + panic!("internal representation corrupt"), + + (&VarState::Unassigned(_), &VarState::Assigned(_)) => + (to, from), + + (&VarState::Assigned(_), &VarState::Unassigned(_)) + | (&VarState::Unassigned(_), &VarState::Unassigned(_)) => + (from, to), + } + }; + + let VarToken(search) = from; + let VarToken(replace) = to; + + // Create new constraints to reflect the unification. + let new_constraints = try!(self.constraints.substitute(from, to)); + self.constraints = Rc::new(new_constraints); + self.wake.union_with(&self.constraints.wake[replace]); + assert!(self.constraints.wake[search].is_empty()); + + // Take intersection of the candidates. + if let &VarState::Assigned(val) = &self.vars[search] { + try!(self.set_candidate(to, val)); + } else { + if let (&mut VarState::Unassigned(Candidates::Set(ref mut rc1)), + &mut VarState::Unassigned(Candidates::Set(ref mut rc2))) + = get_two_mut(&mut self.vars, search, replace) { + *rc2 = Rc::new(rc2.intersection(rc1).cloned().collect()); + if rc2.is_empty() { + return Err(()); + } + } + } + + self.vars[search] = VarState::Unified(to); + Ok(()) + } } impl<'a> fmt::Debug for PuzzleSearch<'a> { @@ -754,6 +838,9 @@ impl<'a> fmt::Debug for PuzzleSearch<'a> { write!(f, " {}", val)?; } }, + &VarState::Unified(VarToken(other)) => { + write!(f, " var {}: var {}", idx, other)?; + }, } } write!(f, "}}")?; @@ -774,6 +861,7 @@ impl<'a> Index for PuzzleSearch<'a> { match &self.vars[idx] { &VarState::Assigned(ref val) => val, &VarState::Unassigned(_) => panic!("unassigned"), + &VarState::Unified(other) => &self[other], } } } @@ -786,6 +874,18 @@ fn bool_to_result(cond: bool) -> PsResult<()> { } } +fn get_two_mut<'a, T>(slice: &'a mut [T], a: usize, b: usize) + -> (&'a mut T, &'a mut T) { + assert!(a != b); + if a < b { + let (mut l, mut r) = slice.split_at_mut(b); + (&mut l[a], &mut r[0]) + } else { + let (l, r) = slice.split_at_mut(a); + (&mut r[0], &mut l[b]) + } +} + #[cfg(test)] mod tests { use ::Puzzle;