puzzle_solver/constraint/
equality.rs

1//! Equality implementation.
2
3use std::rc::Rc;
4use num_rational::Ratio;
5use num_traits::Zero;
6
7use ::{Constraint,LinExpr,PsResult,PuzzleSearch,Val,VarToken};
8
9pub struct Equality {
10    // The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ...
11    eqn: LinExpr,
12}
13
14impl Equality {
15    /// Allocate a new Equality constraint.
16    ///
17    /// # Examples
18    ///
19    /// ```
20    /// let mut magic_square = puzzle_solver::Puzzle::new();
21    /// let vars = magic_square.new_vars_with_candidates_2d(3, 3,
22    ///         &[1,2,3,4,5,6,7,8,9]);
23    ///
24    /// puzzle_solver::constraint::Equality::new(
25    ///         vars[0][0] + vars[0][1] + vars[0][2] - 15);
26    /// ```
27    pub fn new(eqn: LinExpr) -> Self {
28        Equality {
29            eqn: eqn,
30        }
31    }
32}
33
34impl Constraint for Equality {
35    fn vars<'a>(&'a self) -> Box<Iterator<Item=&'a VarToken> + 'a> {
36        Box::new(self.eqn.coef.keys())
37    }
38
39    fn on_assigned(&self, search: &mut PuzzleSearch, _: VarToken, _: Val)
40            -> PsResult<()> {
41        let mut sum = self.eqn.constant;
42        let mut unassigned_var = None;
43
44        for (&var, &coef) in self.eqn.coef.iter() {
45            if let Some(val) = search.get_assigned(var) {
46                sum = sum + coef * Ratio::from_integer(val);
47            } else {
48                // If we find more than one unassigned variable,
49                // cannot assign any other variables.
50                if unassigned_var.is_some() {
51                    return Ok(());
52                } else {
53                    unassigned_var = Some((var, coef));
54                }
55            }
56        }
57
58        // If we have exactly one unassigned variable, assign it.
59        if let Some((var, coef)) = unassigned_var {
60            // sum + coef * var = 0.
61            let val = -sum / coef;
62            if val.is_integer() {
63                try!(search.set_candidate(var, val.to_integer()));
64            } else {
65                return Err(());
66            }
67        } else {
68            if !sum.is_zero() {
69                return Err(());
70            }
71        }
72
73        Ok(())
74    }
75
76    fn on_updated(&self, search: &mut PuzzleSearch) -> PsResult<()> {
77        let mut sum_min = self.eqn.constant;
78        let mut sum_max = self.eqn.constant;
79
80        for (&var, &coef) in self.eqn.coef.iter() {
81            let (min_val, max_val) = try!(search.get_min_max(var));
82            if coef > Ratio::zero() {
83                sum_min = sum_min + coef * Ratio::from_integer(min_val);
84                sum_max = sum_max + coef * Ratio::from_integer(max_val);
85            } else {
86                sum_min = sum_min + coef * Ratio::from_integer(max_val);
87                sum_max = sum_max + coef * Ratio::from_integer(min_val);
88            }
89        }
90
91        // Minimum (maximum) of var can be bounded by summing the
92        // maximum (minimum) of everything else.  Repeat until no
93        // changes further changes to the extremes found.
94        let mut iters = self.eqn.coef.len();
95        let mut iter = self.eqn.coef.iter().cycle();
96        while iters > 0 {
97            iters = iters - 1;
98            if !(sum_min <= Ratio::zero() && Ratio::zero() <= sum_max) {
99                return Err(());
100            }
101
102            let (&var, &coef) = iter.next().expect("cycle");
103            if search.is_assigned(var) {
104                continue;
105            }
106
107            let (min_val, max_val) = try!(search.get_min_max(var));
108            let (min_bnd, max_bnd);
109
110            if coef > Ratio::zero() {
111                min_bnd = ((coef * Ratio::from_integer(max_val) - sum_max) / coef).ceil().to_integer();
112                max_bnd = ((coef * Ratio::from_integer(min_val) - sum_min) / coef).floor().to_integer();
113            } else {
114                min_bnd = ((coef * Ratio::from_integer(max_val) - sum_min) / coef).ceil().to_integer();
115                max_bnd = ((coef * Ratio::from_integer(min_val) - sum_max) / coef).floor().to_integer();
116            }
117
118            if min_val < min_bnd || max_bnd < max_val {
119                let (new_min, new_max)
120                    = try!(search.bound_candidate_range(var, min_bnd, max_bnd));
121
122                if coef > Ratio::zero() {
123                    sum_min = sum_min + coef * Ratio::from_integer(new_min - min_val);
124                    sum_max = sum_max + coef * Ratio::from_integer(new_max - max_val);
125                } else {
126                    sum_min = sum_min + coef * Ratio::from_integer(new_max - max_val);
127                    sum_max = sum_max + coef * Ratio::from_integer(new_min - min_val);
128                }
129
130                iters = self.eqn.coef.len();
131            }
132        }
133
134        Ok(())
135    }
136
137    fn substitute(&self, from: VarToken, to: VarToken)
138            -> PsResult<Rc<Constraint>> {
139        let mut eqn = self.eqn.clone();
140        if let Some(coef) = eqn.coef.remove(&from) {
141            eqn = eqn + coef * to;
142        }
143
144        Ok(Rc::new(Equality{ eqn: eqn }))
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use ::{Puzzle,Val};
151
152    #[test]
153    fn test_contradiction() {
154        let mut puzzle = Puzzle::new();
155        let v0 = puzzle.new_var_with_candidates(&[3]);
156        let v1 = puzzle.new_var_with_candidates(&[0,1]);
157
158        puzzle.equals(v0 + 2 * v1, 4);
159
160        let search = puzzle.step();
161        assert!(search.is_none());
162    }
163
164    #[test]
165    fn test_assign() {
166        let mut puzzle = Puzzle::new();
167        let v0 = puzzle.new_var_with_candidates(&[1]);
168        let v1 = puzzle.new_var_with_candidates(&[1,2,3]);
169
170        puzzle.equals(v0 + v1, 4);
171
172        let search = puzzle.step().expect("contradiction");
173        assert_eq!(search[v0], 1);
174        assert_eq!(search[v1], 3);
175    }
176
177    #[test]
178    fn test_reduce_range() {
179        let mut puzzle = Puzzle::new();
180        let v0 = puzzle.new_var_with_candidates(&[1,2,3]);
181        let v1 = puzzle.new_var_with_candidates(&[3,4,5]);
182
183        puzzle.equals(v0 + v1, 5);
184
185        let search = puzzle.step().expect("contradiction");
186        assert_eq!(search.get_unassigned(v0).collect::<Vec<Val>>(), &[1,2]);
187        assert_eq!(search.get_unassigned(v1).collect::<Vec<Val>>(), &[3,4]);
188    }
189}