use std::mem;
use itertools::Itertools;
use crate::{
IntVal,
actions::{
InitActions, IntDecisionActions, IntInitActions, IntInspectionActions, IntPropCond,
IntPropagationActions, IntSimplificationActions, PropagationActions, ReasoningEngine,
},
constraints::{
Constraint, IntModelActions, IntSolverActions, Propagator, SimplificationStatus,
},
lower::{LoweringContext, LoweringError},
model::View,
solver::{IntLitMeaning, queue::PriorityLevel},
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct IntTable {
pub(crate) vars: Vec<View<IntVal>>,
pub(crate) table: Vec<Vec<IntVal>>,
}
impl<E> Constraint<E> for IntTable
where
E: ReasoningEngine,
View<IntVal>: IntModelActions<E>,
{
fn simplify(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<SimplificationStatus, E::Conflict> {
match self.vars.len() {
0 => return Ok(SimplificationStatus::Subsumed),
1 => {
let dom = self.table.iter().map(|v| v[0]..=v[0]).collect();
self.vars[0].restrict_domain(ctx, &dom, [])?;
return Ok(SimplificationStatus::Subsumed);
}
_ => {}
}
self.table = mem::take(&mut self.table)
.into_iter()
.filter(|tup| {
tup.iter()
.enumerate()
.all(|(j, val)| self.vars[j].in_domain(ctx, *val))
})
.collect_vec();
if self.table.is_empty() {
return Err(ctx.declare_conflict([]));
}
if self.table.len() == 1 {
for (j, &var) in self.vars.iter().enumerate() {
var.fix(ctx, self.table[0][j], [])?;
}
return Ok(SimplificationStatus::Subsumed);
}
for (j, &var) in self.vars.iter().enumerate() {
let dom = (0..self.table.len())
.map(|i| self.table[i][j]..=self.table[i][j])
.collect();
var.restrict_domain(ctx, &dom, [])?;
}
Ok(SimplificationStatus::NoFixpoint)
}
fn to_solver(&self, slv: &mut LoweringContext<'_>) -> Result<(), LoweringError> {
assert!(self.vars.len() >= 2);
let selector = if self.vars.len() != 2 {
(0..self.table.len())
.map(|_| slv.new_bool_decision())
.collect()
} else {
Vec::new()
};
let vars = self
.vars
.iter()
.map(|&iv| slv.solver_view(iv))
.collect_vec();
if vars.len() != 2 {
for (i, tup) in self.table.iter().enumerate() {
assert!(tup.len() == vars.len());
for (j, var) in vars.iter().enumerate() {
let clause = [!selector[i], var.lit(slv, IntLitMeaning::Eq(tup[j]))];
slv.add_clause(clause)?;
}
}
}
for (j, var) in vars.iter().enumerate() {
let (lb, ub) = var.bounds(slv);
let mut support_clauses: Vec<Vec<_>> = vec![Vec::new(); (ub - lb + 1) as usize];
for (i, tup) in self.table.iter().enumerate() {
let k = tup[j] - lb;
if !(0..support_clauses.len() as IntVal).contains(&k) {
continue;
}
if vars.len() == 2 {
support_clauses[k as usize]
.push(vars[1 - j].lit(slv, IntLitMeaning::Eq(tup[1 - j])));
} else {
support_clauses[k as usize].push(selector[i]);
}
}
for (i, mut clause) in support_clauses.into_iter().enumerate() {
if var.in_domain(slv, lb + i as IntVal) {
clause.push(vars[j].lit(slv, IntLitMeaning::NotEq(lb + i as IntVal)));
slv.add_clause(clause)?;
}
}
}
Ok(())
}
}
impl<E> Propagator<E> for IntTable
where
E: ReasoningEngine,
View<IntVal>: IntSolverActions<E>,
{
fn initialize(&mut self, ctx: &mut E::InitializationContext<'_>) {
ctx.set_priority(PriorityLevel::Low);
for var in &self.vars {
var.enqueue_when(ctx, IntPropCond::Domain);
}
}
fn propagate(&mut self, _: &mut E::PropagationContext<'_>) -> Result<(), E::Conflict> {
unreachable!()
}
}
#[cfg(test)]
mod tests {
use expect_test::expect;
use itertools::Itertools;
use crate::model::Model;
#[test]
fn test_binary_table_sat() {
let mut prb = Model::default();
let vars = prb.new_int_decisions(3, 1..=5);
let table = vec![
vec![1, 3],
vec![1, 4],
vec![2, 4],
vec![2, 5],
vec![3, 1],
vec![3, 5],
vec![4, 1],
vec![4, 2],
vec![5, 2],
vec![5, 3],
];
prb.table(vec![vars[0], vars[1]])
.values(table.clone())
.post()
.unwrap();
prb.table(vec![vars[1], vars[2]])
.values(table.clone())
.post()
.unwrap();
let (mut slv, map) = prb.lower().to_solver().unwrap();
let vars = vars.into_iter().map(|x| map.get(&mut slv, x)).collect_vec();
slv.expect_solutions(
&vars,
expect![[r#"
1, 3, 1
1, 3, 5
1, 4, 1
1, 4, 2
2, 4, 1
2, 4, 2
2, 5, 2
2, 5, 3
3, 1, 3
3, 1, 4
3, 5, 2
3, 5, 3
4, 1, 3
4, 1, 4
4, 2, 4
4, 2, 5
5, 2, 4
5, 2, 5
5, 3, 1
5, 3, 5"#]],
);
}
#[test]
fn test_tertiary_table_sat() {
let mut prb = Model::default();
let vars = prb.new_int_decisions(5, 1..=5);
let table = vec![
vec![1, 3, 1],
vec![1, 3, 5],
vec![2, 4, 2],
vec![3, 1, 3],
vec![3, 5, 3],
vec![4, 2, 4],
vec![5, 3, 1],
vec![5, 3, 5],
];
prb.table(vars[0..3].iter().cloned())
.values(table.clone())
.post()
.unwrap();
prb.table(vars[2..5].iter().cloned())
.values(table.clone())
.post()
.unwrap();
let (mut slv, map) = prb.lower().to_solver().unwrap();
let vars = vars.into_iter().map(|x| map.get(&mut slv, x)).collect_vec();
slv.expect_solutions(
&vars,
expect![[r#"
1, 3, 1, 3, 1
1, 3, 1, 3, 5
1, 3, 5, 3, 1
1, 3, 5, 3, 5
2, 4, 2, 4, 2
3, 1, 3, 1, 3
3, 1, 3, 5, 3
3, 5, 3, 1, 3
3, 5, 3, 5, 3
4, 2, 4, 2, 4
5, 3, 1, 3, 1
5, 3, 1, 3, 5
5, 3, 5, 3, 1
5, 3, 5, 3, 5"#]],
);
}
}