puzzle_solver/constraint/
equality.rs1use std::rc::Rc;
4use num_rational::Ratio;
5use num_traits::Zero;
6
7use ::{Constraint,LinExpr,PsResult,PuzzleSearch,Val,VarToken};
8
9pub struct Equality {
10 eqn: LinExpr,
12}
13
14impl Equality {
15 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 unassigned_var.is_some() {
51 return Ok(());
52 } else {
53 unassigned_var = Some((var, coef));
54 }
55 }
56 }
57
58 if let Some((var, coef)) = unassigned_var {
60 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 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}