pumpkin_constraints/constraints/
table.rs1use std::collections::BTreeMap;
2
3use pumpkin_core::Solver;
4use pumpkin_core::constraints::Constraint;
5use pumpkin_core::constraints::NegatableConstraint;
6use pumpkin_core::predicate;
7use pumpkin_core::proof::ConstraintTag;
8use pumpkin_core::variables::IntegerVariable;
9use pumpkin_core::variables::Literal;
10
11pub fn table<Var: IntegerVariable + 'static>(
23 xs: impl IntoIterator<Item = Var>,
24 table: Vec<Vec<i32>>,
25 constraint_tag: ConstraintTag,
26) -> impl NegatableConstraint {
27 Table {
28 xs: xs.into_iter().collect(),
29 table,
30 constraint_tag,
31 }
32}
33
34pub fn negative_table<Var: IntegerVariable + 'static>(
46 xs: impl IntoIterator<Item = Var>,
47 table: Vec<Vec<i32>>,
48 constraint_tag: ConstraintTag,
49) -> impl NegatableConstraint {
50 NegativeTable {
51 xs: xs.into_iter().collect(),
52 table,
53 constraint_tag,
54 }
55}
56
57struct Table<Var> {
58 xs: Vec<Var>,
59 table: Vec<Vec<i32>>,
60 constraint_tag: ConstraintTag,
61}
62
63impl<Var: IntegerVariable> Table<Var> {
64 fn encode(self, solver: &mut Solver, reification_literal: Option<Literal>) {
65 let ys: Vec<_> = (0..self.table.len())
67 .map(|_| solver.new_literal())
68 .collect();
69
70 for (col, x_col) in self.xs.iter().enumerate() {
72 let mut values = BTreeMap::new();
74
75 for (row, &y_row) in ys.iter().enumerate() {
77 let value = self.table[row][col];
78
79 let supports = values.entry(value).or_insert(vec![]);
80 supports.push(y_row);
81 }
82
83 for (value, supports) in values {
86 let condition = predicate![x_col == value];
87
88 for support in supports.iter() {
90 let mut clause = vec![support.get_false_predicate(), condition];
91
92 clause.extend(reification_literal.iter().map(|l| l.get_false_predicate()));
95
96 solver.add_clause(clause, self.constraint_tag);
97 }
98
99 let mut clause = vec![!condition];
101 clause.extend(supports.iter().map(|l| l.get_true_predicate()));
102 clause.extend(reification_literal.iter().map(|l| l.get_false_predicate()));
104
105 solver.add_clause(clause, self.constraint_tag);
106 }
107 }
108
109 let poster = solver.add_constraint(crate::constraints::clause(ys, self.constraint_tag));
111 if let Some(literal) = reification_literal {
112 poster.implied_by(literal);
113 } else {
114 poster.post();
115 }
116 }
117}
118
119impl<Var: IntegerVariable> Constraint for Table<Var> {
120 fn post(self, solver: &mut Solver) {
121 self.encode(solver, None)
122 }
123
124 fn implied_by(self, solver: &mut Solver, reification_literal: Literal) {
125 self.encode(solver, Some(reification_literal))
126 }
127}
128
129impl<Var: IntegerVariable + 'static> NegatableConstraint for Table<Var> {
130 type NegatedConstraint = NegativeTable<Var>;
131
132 fn negation(&self) -> Self::NegatedConstraint {
133 let xs = self.xs.clone();
134 let table = self.table.clone();
135 let constraint_tag = self.constraint_tag;
136
137 NegativeTable {
138 xs,
139 table,
140 constraint_tag,
141 }
142 }
143}
144
145struct NegativeTable<Var> {
146 xs: Vec<Var>,
147 table: Vec<Vec<i32>>,
148 constraint_tag: ConstraintTag,
149}
150
151impl<Var: IntegerVariable> Constraint for NegativeTable<Var> {
152 fn post(self, solver: &mut Solver) {
153 for row in self.table {
154 let clause: Vec<_> = self
155 .xs
156 .iter()
157 .zip(row)
158 .map(|(x, value)| predicate![x != value])
159 .collect();
160
161 solver.add_clause(clause, self.constraint_tag);
162 }
163 }
164
165 fn implied_by(self, solver: &mut Solver, reification_literal: Literal) {
166 for row in self.table {
167 let clause: Vec<_> = self
168 .xs
169 .iter()
170 .zip(row)
171 .map(|(x, value)| predicate![x != value])
172 .chain(std::iter::once(reification_literal.get_false_predicate()))
173 .collect();
174
175 solver.add_clause(clause, self.constraint_tag);
176 }
177 }
178}
179
180impl<Var: IntegerVariable + 'static> NegatableConstraint for NegativeTable<Var> {
181 type NegatedConstraint = Table<Var>;
182
183 fn negation(&self) -> Self::NegatedConstraint {
184 let xs = self.xs.clone();
185 let table = self.table.clone();
186 let constraint_tag = self.constraint_tag;
187
188 Table {
189 xs,
190 table,
191 constraint_tag,
192 }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use pumpkin_core::results::CSPSolverExecutionFlag;
199
200 use super::*;
201
202 #[test]
203 fn eliminating_all_supporting_rows_prunes_the_value() {
204 let mut solver = Solver::default();
205
206 let constraint_tag = solver.new_constraint_tag();
207 let x1 = solver.new_named_bounded_integer(1, 2, "x1");
208 let x2 = solver.new_named_sparse_integer([10, 20, 30], "x2");
209
210 let rows = vec![vec![1, 10], vec![1, 20], vec![2, 30], vec![2, 30]];
214 table(vec![x1, x2], rows, constraint_tag).post(&mut solver);
215
216 let result = solver.propagate_to_fixpoint();
217 assert_eq!(result, CSPSolverExecutionFlag::Feasible);
218
219 solver.add_clause([predicate![x1 == 1]], constraint_tag);
220
221 let result = solver.propagate_to_fixpoint();
222 assert_eq!(result, CSPSolverExecutionFlag::Feasible);
223
224 assert_eq!(solver.upper_bound(&x2), 20);
225 }
226}