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.
This commit is contained in:
David Wang 2017-03-11 10:06:42 +11:00
parent 4a331fdc1a
commit f265a165dd
3 changed files with 223 additions and 12 deletions

View File

@ -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;

109
src/constraint/unify.rs Normal file
View File

@ -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<Iterator<Item=&'a VarToken> + '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<Rc<Constraint>> {
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);
}
}

View File

@ -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<Self> {
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<Rc<Constraint>>, num_vars: usize)
-> Vec<BitSet> {
@ -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<VarToken> 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;