pumpkin_core/constraints/
table.rs

1use std::collections::BTreeMap;
2
3use super::Constraint;
4use super::NegatableConstraint;
5use crate::predicate;
6use crate::proof::ConstraintTag;
7use crate::variables::IntegerVariable;
8use crate::variables::Literal;
9use crate::ConstraintOperationError;
10use crate::Solver;
11
12/// Create a table constraint over the variables `xs`.
13///
14/// A table constraint constrains a tuple of variables to have pre-defined values. For example:
15/// ```ignore
16/// (x1, x2, x3) in {(1, 3, 5), (3, 1, 4)}
17/// ```
18/// This has two solutions: either the first tuple of values is assigned to the variables, or the
19/// second. The set of value tuples is the 'table'.
20///
21/// In the XCSP3 specification, this is the "positive table"
22/// (<https://www.xcsp.org/specifications/constraints/generic/extension/>).
23pub fn table<Var: IntegerVariable + 'static>(
24    xs: impl IntoIterator<Item = Var>,
25    table: Vec<Vec<i32>>,
26    constraint_tag: ConstraintTag,
27) -> impl NegatableConstraint {
28    Table {
29        xs: xs.into_iter().collect(),
30        table,
31        constraint_tag,
32    }
33}
34
35/// Create a negative table constraint over the variables `xs`.
36///
37/// A negative table is essentially a set of conflicts over the given variables. For example:
38/// ```ignore
39/// (x1, x2, x3) not in {(1, 3, 5), (3, 1, 4)}
40/// ```
41/// This prevents any solution where the variables have both the first and the second tuple as
42/// values.
43///
44/// In the XCSP3 specification, this is the "negative table"
45/// (<https://www.xcsp.org/specifications/constraints/generic/extension/>).
46pub fn negative_table<Var: IntegerVariable + 'static>(
47    xs: impl IntoIterator<Item = Var>,
48    table: Vec<Vec<i32>>,
49    constraint_tag: ConstraintTag,
50) -> impl NegatableConstraint {
51    NegativeTable {
52        xs: xs.into_iter().collect(),
53        table,
54        constraint_tag,
55    }
56}
57
58struct Table<Var> {
59    xs: Vec<Var>,
60    table: Vec<Vec<i32>>,
61    constraint_tag: ConstraintTag,
62}
63
64impl<Var: IntegerVariable> Table<Var> {
65    fn encode(
66        self,
67        solver: &mut Solver,
68        reification_literal: Option<Literal>,
69    ) -> Result<(), ConstraintOperationError> {
70        // 1. Create a variable `y_i` that selects the row from the table which is chosen.
71        let ys: Vec<_> = (0..self.table.len())
72            .map(|_| solver.new_literal())
73            .collect();
74
75        // 2. Setup the implications between values and `ys`.
76        for (col, x_col) in self.xs.iter().enumerate() {
77            // A map from domain values to the `ys` variables that support this value.
78            let mut values = BTreeMap::new();
79
80            // For every value in this column, aggregate the `ys` that support it.
81            for (row, &y_row) in ys.iter().enumerate() {
82                let value = self.table[row][col];
83
84                let supports = values.entry(value).or_insert(vec![]);
85                supports.push(y_row);
86            }
87
88            // For every value in this column, add the clause
89            //   `condition <-> (\/ supports)`
90            for (value, supports) in values {
91                let condition = predicate![x_col == value];
92
93                // For every `support in supports`: `support -> condition`
94                for support in supports.iter() {
95                    let mut clause = vec![support.get_false_predicate(), condition];
96
97                    // Account for possible reification.
98                    // l -> clause
99                    clause.extend(reification_literal.iter().map(|l| l.get_false_predicate()));
100
101                    solver.add_clause(clause, self.constraint_tag)?;
102                }
103
104                // `condition -> (\/ supports)`
105                let mut clause = vec![!condition];
106                clause.extend(supports.iter().map(|l| l.get_true_predicate()));
107                // Account for possible reification.
108                clause.extend(reification_literal.iter().map(|l| l.get_false_predicate()));
109            }
110        }
111
112        // 4. Enforce at least one `y` to be true.
113        let poster = solver.add_constraint(crate::constraints::clause(ys, self.constraint_tag));
114        if let Some(literal) = reification_literal {
115            poster.implied_by(literal)?;
116        } else {
117            poster.post()?;
118        }
119
120        Ok(())
121    }
122}
123
124impl<Var: IntegerVariable> Constraint for Table<Var> {
125    fn post(self, solver: &mut Solver) -> Result<(), ConstraintOperationError> {
126        self.encode(solver, None)
127    }
128
129    fn implied_by(
130        self,
131        solver: &mut Solver,
132        reification_literal: Literal,
133    ) -> Result<(), ConstraintOperationError> {
134        self.encode(solver, Some(reification_literal))
135    }
136}
137
138impl<Var: IntegerVariable + 'static> NegatableConstraint for Table<Var> {
139    type NegatedConstraint = NegativeTable<Var>;
140
141    fn negation(&self) -> Self::NegatedConstraint {
142        let xs = self.xs.clone();
143        let table = self.table.clone();
144        let constraint_tag = self.constraint_tag;
145
146        NegativeTable {
147            xs,
148            table,
149            constraint_tag,
150        }
151    }
152}
153
154struct NegativeTable<Var> {
155    xs: Vec<Var>,
156    table: Vec<Vec<i32>>,
157    constraint_tag: ConstraintTag,
158}
159
160impl<Var: IntegerVariable> Constraint for NegativeTable<Var> {
161    fn post(self, solver: &mut Solver) -> Result<(), ConstraintOperationError> {
162        for row in self.table {
163            let clause: Vec<_> = self
164                .xs
165                .iter()
166                .zip(row)
167                .map(|(x, value)| predicate![x != value])
168                .collect();
169
170            solver.add_clause(clause, self.constraint_tag)?;
171        }
172
173        Ok(())
174    }
175
176    fn implied_by(
177        self,
178        solver: &mut Solver,
179        reification_literal: Literal,
180    ) -> Result<(), ConstraintOperationError> {
181        for row in self.table {
182            let clause: Vec<_> = self
183                .xs
184                .iter()
185                .zip(row)
186                .map(|(x, value)| predicate![x != value])
187                .chain(std::iter::once(reification_literal.get_false_predicate()))
188                .collect();
189
190            solver.add_clause(clause, self.constraint_tag)?;
191        }
192
193        Ok(())
194    }
195}
196
197impl<Var: IntegerVariable + 'static> NegatableConstraint for NegativeTable<Var> {
198    type NegatedConstraint = Table<Var>;
199
200    fn negation(&self) -> Self::NegatedConstraint {
201        let xs = self.xs.clone();
202        let table = self.table.clone();
203        let constraint_tag = self.constraint_tag;
204
205        Table {
206            xs,
207            table,
208            constraint_tag,
209        }
210    }
211}