use std::cmp::{max, min};
use crate::{
Conjunction, IntVal,
actions::{
ConstructionActions, InitActions, IntDecisionActions, IntInspectionActions, IntPropCond,
PostingActions, ReasoningContext, ReasoningEngine, Trailed, TrailingActions,
},
constraints::{
Constraint, IntModelActions, IntSolverActions, Propagator, SimplificationStatus,
},
lower::{LoweringContext, LoweringError},
solver::{IntLitMeaning, engine::Engine, queue::PriorityLevel},
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct IntSeqPrecedeChainBounds<I> {
vars: Vec<I>,
initialized: bool,
first: Vec<Trailed<IntVal>>,
last: Vec<Trailed<IntVal>>,
first_val: Vec<Trailed<IntVal>>,
max_last: Trailed<IntVal>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct IntValuePrecedeChainValue<I> {
values: Vec<IntVal>,
vars: Vec<I>,
initialized: bool,
first: Vec<Trailed<IntVal>>,
last: Vec<Trailed<IntVal>>,
first_val: Vec<Trailed<IntVal>>,
max_last: Trailed<IntVal>,
min_val: IntVal,
max_val: IntVal,
min_hole: IntVal,
next_hole: Vec<IntVal>,
holes: Vec<IntVal>,
mapping: Vec<Option<usize>>,
}
impl<I> IntSeqPrecedeChainBounds<I> {
fn explain_lower<Ctx>(
&self,
i: usize,
k: IntVal,
) -> impl FnOnce(&mut Ctx) -> Conjunction<Ctx::Atom> + '_
where
Ctx: ReasoningContext + ?Sized,
I: IntDecisionActions<Ctx>,
{
move |ctx: &mut Ctx| {
let mut reason = Vec::new();
{
let mut i = i + 1;
let mut k = k;
loop {
if self.vars[i].min(ctx) > k {
reason.push(self.vars[i].lit(ctx, IntLitMeaning::GreaterEq(k + 1)));
break;
}
if self.vars[i].in_domain(ctx, k) {
i += 1;
k += 1;
} else {
reason.push(self.vars[i].lit(ctx, IntLitMeaning::NotEq(k)));
i += 1;
}
}
}
reason.extend(self.explain_upper(i, k)(ctx));
reason
}
}
fn explain_upper<Ctx>(
&self,
i: usize,
k: IntVal,
) -> impl FnOnce(&mut Ctx) -> Conjunction<Ctx::Atom> + '_
where
Ctx: ReasoningContext + ?Sized,
I: IntDecisionActions<Ctx>,
{
move |ctx: &mut Ctx| {
self.vars
.iter()
.take(i)
.map(|v| v.lit(ctx, IntLitMeaning::Less(k)))
.collect()
}
}
fn initial_propagation<E>(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<(), E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let mut up = 0;
let mut low = None;
for (i, v) in self.vars.iter().enumerate() {
let mut ub_v = v.max(ctx);
if ub_v > up + 1 {
if v.in_domain(ctx, up + 1) {
ub_v = up + 1;
}
v.tighten_max(ctx, up + 1, self.explain_upper(i, up + 1))?;
}
if ub_v == up + 1 {
up += 1;
ctx.set_trailed(self.first[up as usize], i as IntVal);
ctx.set_trailed(self.first_val[i], up);
}
let lb_v = v.min(ctx);
if lb_v > 0 && low.is_none_or(|v| v < lb_v) {
ctx.set_trailed(self.last[lb_v as usize], i as IntVal);
low = Some(lb_v);
}
}
if let Some(mut low) = low {
ctx.set_trailed(self.max_last, low);
for (i, v) in self.vars.iter().enumerate().rev() {
if ctx.trailed(self.first[low as usize]) == i as IntVal {
v.tighten_min(ctx, low, self.explain_lower(i, low))?;
}
if i as IntVal <= ctx.trailed(self.last[low as usize]) && v.in_domain(ctx, low) {
ctx.set_trailed(self.last[low as usize], i as IntVal);
low -= 1;
}
if low == 0 {
break;
}
}
}
self.initialized = true;
Ok(())
}
pub(crate) fn new<E>(engine: &mut E, vars: Vec<I>) -> Self
where
E: ConstructionActions + ReasoningContext + ?Sized,
I: IntInspectionActions<E>,
{
let n = vars.len();
let ub = vars
.iter()
.fold(0, |u, item| if item.max(engine) > u { u + 1 } else { u });
let first = (0..=ub).map(|_| engine.new_trailed(0)).collect();
let last = (0..=ub)
.map(|i| engine.new_trailed(if i == 0 { IntVal::MIN } else { IntVal::MAX }))
.collect();
let first_val = (0..n).map(|_| engine.new_trailed(0)).collect();
let max_last = engine.new_trailed(0);
Self {
vars: vars.clone(),
initialized: false,
first,
last,
first_val,
max_last,
}
}
pub fn post<E>(solver: &mut E, mut vars: Vec<I>)
where
E: PostingActions + ?Sized,
I: IntSolverActions<Engine> + IntInspectionActions<E>,
{
vars.retain(|v| v.max(solver) > 0);
if vars.is_empty() {
return;
}
vars.shrink_to_fit();
let con = IntSeqPrecedeChainBounds::new(solver, vars);
solver.add_propagator(Box::new(con));
}
fn repair_lower<E>(
&self,
ctx: &mut E::PropagationContext<'_>,
mut k: IntVal,
) -> Result<(IntVal, IntVal), E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let mut i = ctx.trailed(self.last[k as usize]);
while k > 0 {
if self.vars[i as usize].in_domain(ctx, k) {
ctx.set_trailed(self.last[k as usize], i);
if ctx.trailed(self.first[k as usize]) == i {
self.vars[i as usize].tighten_min(ctx, k, self.explain_lower(i as usize, k))?;
}
k -= 1;
if ctx.trailed(self.last[k as usize]) < i {
return Ok((i, k + 1));
}
}
i -= 1;
if i < 0 {
self.vars[0].tighten_min(ctx, k, self.explain_lower(0, k))?;
}
}
Ok((i, 0))
}
fn repair_upper<E>(
&self,
ctx: &mut E::PropagationContext<'_>,
mut k: IntVal,
) -> Result<(), E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let mut i = ctx.trailed(self.first[k as usize]);
let mut lim = self.upper_limit(ctx, k as usize);
while i <= lim {
if self.vars[i as usize].max(ctx) > k {
self.vars[i as usize].tighten_max(ctx, k, self.explain_upper(i as usize, k))?;
}
if self.vars[i as usize].in_domain(ctx, k) {
ctx.set_trailed(self.first[k as usize], i);
ctx.set_trailed(self.first_val[i as usize], k);
if ctx.trailed(self.last[k as usize]) == i {
self.vars[i as usize].tighten_min(ctx, k, self.explain_lower(i as usize, k))?;
}
k += 1;
if (k as usize) == self.first.len() || i < ctx.trailed(self.first[k as usize]) {
return Ok(());
}
lim = self.upper_limit(ctx, k as usize);
}
i += 1;
}
if (i as usize) < self.vars.len() {
self.vars[i as usize - 1].tighten_min(ctx, k, self.explain_lower(i as usize - 1, k))?;
}
ctx.set_trailed(self.first[k as usize], 0);
Ok(())
}
fn upper_limit<Ctx: TrailingActions>(&self, ctx: &mut Ctx, k: usize) -> IntVal {
min(ctx.trailed(self.last[k]), self.vars.len() as IntVal - 1)
}
}
impl<E, I> Constraint<E> for IntSeqPrecedeChainBounds<I>
where
E: ReasoningEngine,
I: IntModelActions<E>,
{
fn simplify(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<SimplificationStatus, E::Conflict> {
if !self.initialized {
self.vars.retain(|v| v.max(ctx) > 0);
self.vars.shrink_to_fit();
}
self.propagate(ctx)?;
if self.vars.iter().all(|v| v.val(ctx).is_some()) {
return Ok(SimplificationStatus::Subsumed);
}
Ok(SimplificationStatus::NoFixpoint)
}
fn to_solver(&self, slv: &mut LoweringContext<'_>) -> Result<(), LoweringError> {
let vars: Vec<_> = self
.vars
.iter()
.map(|v| slv.solver_view(v.clone().into()))
.collect();
IntSeqPrecedeChainBounds::post(slv, vars);
Ok(())
}
}
impl<E, I> Propagator<E> for IntSeqPrecedeChainBounds<I>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
fn initialize(&mut self, ctx: &mut E::InitializationContext<'_>) {
ctx.set_priority(PriorityLevel::Low);
for v in &self.vars {
v.enqueue_when(ctx, IntPropCond::Domain);
}
}
#[tracing::instrument(
name = "int_seq_precede_chain_bounds",
target = "solver",
level = "trace",
skip(self, ctx)
)]
fn propagate(&mut self, ctx: &mut E::PropagationContext<'_>) -> Result<(), E::Conflict> {
if !self.initialized {
return self.initial_propagation(ctx);
}
for (k, &t) in self.first.iter().enumerate() {
let i = ctx.trailed(t);
if ctx.trailed(self.first_val[i as usize]) == k as IntVal
&& self.vars[i as usize].max(ctx) < k as IntVal
{
self.repair_upper(ctx, k as IntVal)?;
}
}
let mut i = self.vars.len() as IntVal;
let mut k = ctx.trailed(self.max_last);
while i > 0 {
i -= 1;
if k > 0 && ctx.trailed(self.last[k as usize - 1]) == i {
k -= 1;
}
let lb = self.vars[i as usize].min(ctx);
if lb > k {
ctx.set_trailed(self.last[lb as usize], i);
if lb > ctx.trailed(self.max_last) {
ctx.set_trailed(self.max_last, lb);
}
(i, k) = self.repair_lower(ctx, lb)?;
continue;
}
if ctx.trailed(self.last[k as usize]) == i && !self.vars[i as usize].in_domain(ctx, k) {
(i, k) = self.repair_lower(ctx, k)?;
}
}
Ok(())
}
}
impl<I> IntValuePrecedeChainValue<I> {
fn explain_lower<Ctx>(
&self,
i: usize,
j: usize,
) -> impl FnOnce(&mut Ctx) -> Conjunction<Ctx::Atom> + '_
where
Ctx: ReasoningContext + ?Sized,
I: IntDecisionActions<Ctx>,
{
move |ctx: &mut Ctx| {
let mut reason = Vec::new();
{
let mut i = i + 1;
let mut j = j;
while j < self.values.len() {
if let Some(lb) = self.lowest_index(ctx, i).unwrap_or(Some(j + 1))
&& lb > j
{
reason.push(self.vars[i].lit(ctx, IntLitMeaning::GreaterEq(self.min_val)));
reason.push(self.vars[i].lit(ctx, IntLitMeaning::Less(self.max_val + 1)));
reason.extend(
self.holes
.iter()
.map(|&h| self.vars[i].lit(ctx, IntLitMeaning::NotEq(h))),
);
reason.extend(
(0..j).map(|k| {
self.vars[i].lit(ctx, IntLitMeaning::NotEq(self.values[k]))
}),
);
break;
}
if self.vars[i].in_domain(ctx, self.values[j - 1]) {
i += 1;
j += 1;
} else {
reason
.push(self.vars[i].lit(ctx, IntLitMeaning::NotEq(self.values[j - 1])));
i += 1;
}
}
}
if j > 0 {
reason.extend(self.explain_upper(i, j)(ctx));
}
reason
}
}
fn explain_upper<Ctx>(
&self,
i: usize,
j: usize,
) -> impl FnOnce(&mut Ctx) -> Conjunction<Ctx::Atom> + '_
where
Ctx: ReasoningContext + ?Sized,
I: IntDecisionActions<Ctx>,
{
move |ctx: &mut Ctx| {
self.vars
.iter()
.take(i)
.map(|v| v.lit(ctx, IntLitMeaning::NotEq(self.values[j - 1])))
.collect()
}
}
fn initial_propagation<E>(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<(), E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let mut up = 0;
let mut low = None;
for (i, v) in self.vars.iter().enumerate() {
self.propagate_max(ctx, i, up + 1)?;
if up < self.values.len() && v.in_domain(ctx, self.values[up]) {
up += 1;
ctx.set_trailed(self.first[up], i as IntVal);
ctx.set_trailed(self.first_val[i], up as IntVal);
}
if let Ok(Some(lb)) = self.lowest_index(ctx, i)
&& low.is_none_or(|v| v < lb)
{
ctx.set_trailed(self.last[lb], i as IntVal);
low = Some(lb);
}
}
if let Some(mut low) = low {
for (i, v) in self.vars.iter().enumerate().rev() {
if ctx.trailed(self.first[low]) == i as IntVal {
self.propagate_min(ctx, i, low)?;
}
if i as IntVal <= ctx.trailed(self.last[low])
&& v.in_domain(ctx, self.values[low - 1])
{
ctx.set_trailed(self.last[low], i as IntVal);
low -= 1;
}
if low == 0 {
break;
}
}
}
self.initialized = true;
Ok(())
}
fn lowest_index<Ctx>(&self, ctx: &mut Ctx, i: usize) -> Result<Option<usize>, ()>
where
Ctx: ReasoningContext + ?Sized,
I: IntInspectionActions<Ctx>,
{
let (lb, ub) = self.vars[i].bounds(ctx);
if lb < self.min_val || ub > self.max_val {
return Ok(None);
}
if lb == ub {
return Ok(self.mapping[(lb - self.min_val) as usize]);
}
let mut h = max(lb, self.min_hole);
while ((h - self.min_hole) as usize) < self.next_hole.len() {
h = self.next_hole[(h - self.min_hole) as usize];
if h > ub {
break;
}
if self.vars[i].in_domain(ctx, h) {
return Ok(None);
}
h += 1;
}
for (j, &val) in self.values.iter().enumerate() {
if self.vars[i].in_domain(ctx, val) {
return Ok(Some(j + 1));
}
}
Err(())
}
pub(crate) fn new<E>(engine: &mut E, values: Vec<IntVal>, vars: Vec<I>) -> Self
where
E: ConstructionActions + ?Sized,
{
let first = (0..=values.len())
.map(|i| {
if i == 0 {
engine.new_trailed(0)
} else {
engine.new_trailed(vars.len() as IntVal - 1)
}
})
.collect();
let last = (0..=values.len())
.map(|i| engine.new_trailed(if i == 0 { IntVal::MIN } else { IntVal::MAX }))
.collect();
let first_val = (0..vars.len()).map(|_| engine.new_trailed(0)).collect();
let max_last = engine.new_trailed(0);
let min_val = *values.iter().min().unwrap_or(&IntVal::MAX);
let max_val = *values.iter().max().unwrap_or(&IntVal::MIN);
let holes = (min_val..=max_val)
.filter(|&i| values.iter().all(|&v| v != i))
.collect::<Vec<_>>();
let min_hole = *holes.iter().min().unwrap_or(&0);
let mut next_hole = vec![0; (*holes.iter().max().unwrap_or(&-1) - min_hole + 1) as usize];
let mut cur_hole = 0;
for (i, h) in next_hole.iter_mut().enumerate() {
if i as IntVal + min_hole > holes[cur_hole] {
cur_hole += 1;
}
*h = holes[cur_hole];
}
let mut mapping = vec![None; (max_val - min_val + 1) as usize];
for (i, &val) in values.iter().enumerate() {
mapping[(val - min_val) as usize] = Some(i + 1);
}
Self {
values,
vars,
initialized: false,
first,
last,
first_val,
max_last,
min_val,
max_val,
holes,
min_hole,
next_hole,
mapping,
}
}
pub fn post<E>(solver: &mut E, values: Vec<IntVal>, mut vars: Vec<I>)
where
E: PostingActions + ?Sized,
I: IntSolverActions<Engine> + IntInspectionActions<E>,
{
vars.retain(|var| values.iter().any(|&val| var.in_domain(solver, val)));
if vars.is_empty() {
return;
}
vars.shrink_to_fit();
let con = IntValuePrecedeChainValue::new(solver, values, vars);
solver.add_propagator(Box::new(con));
}
fn propagate_max<E>(
&self,
ctx: &mut E::PropagationContext<'_>,
i: usize,
j: usize,
) -> Result<(), E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
for k in j..self.values.len() {
if self.vars[i].in_domain(ctx, self.values[k]) {
self.vars[i].remove_val(ctx, self.values[k], self.explain_upper(i, k))?;
}
}
Ok(())
}
fn propagate_min<E>(
&self,
ctx: &mut E::PropagationContext<'_>,
i: usize,
j: usize,
) -> Result<(), E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let (lb, ub) = self.vars[i].bounds(ctx);
if lb < self.min_val {
self.vars[i].tighten_min(ctx, self.min_val, self.explain_lower(i, j))?;
}
if ub > self.max_val {
self.vars[i].tighten_max(ctx, self.max_val, self.explain_lower(i, j))?;
}
let mut h = max(lb, self.min_hole);
while ((h - self.min_hole) as usize) < self.next_hole.len() {
h = self.next_hole[(h - self.min_hole) as usize];
if h > ub {
break;
}
if self.vars[i].in_domain(ctx, h) {
self.vars[i].remove_val(ctx, h, self.explain_lower(i, j))?;
}
h += 1;
}
for k in 0..j - 1 {
if self.vars[i].in_domain(ctx, self.values[k]) {
self.vars[i].remove_val(ctx, self.values[k], self.explain_lower(i, j))?;
}
}
Ok(())
}
fn repair_lower<E>(
&self,
ctx: &mut E::PropagationContext<'_>,
mut k: usize,
) -> Result<(usize, usize), E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let mut i = ctx.trailed(self.last[k]);
while k > 0 {
if self.vars[i as usize].in_domain(ctx, self.values[k - 1]) {
ctx.set_trailed(self.last[k], i);
if ctx.trailed(self.first[k]) == i {
self.propagate_min(ctx, i as usize, k)?;
}
k -= 1;
if ctx.trailed(self.last[k]) < i {
return Ok((i as usize, k + 1));
}
}
i -= 1;
if i < 0 {
self.propagate_min(ctx, 0, k)?;
return Ok((0, k));
}
}
if i < 0 {
return Ok((0, 0));
}
Ok((i as usize, 0))
}
fn repair_upper<E>(
&self,
ctx: &mut E::PropagationContext<'_>,
mut k: usize,
) -> Result<(), E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let mut i = ctx.trailed(self.first[k]);
let mut lim = self.upper_limit(ctx, k);
while i <= lim {
self.propagate_max(ctx, i as usize, k)?;
if self.vars[i as usize].in_domain(ctx, self.values[k - 1]) {
ctx.set_trailed(self.first[k], i);
ctx.set_trailed(self.first_val[i as usize], k as IntVal);
if ctx.trailed(self.last[k]) == i {
self.propagate_min(ctx, i as usize, k)?;
}
k += 1;
if k == self.first.len() || i < ctx.trailed(self.first[k]) {
return Ok(());
}
lim = self.upper_limit(ctx, k);
}
i += 1;
}
if (i as usize) < self.vars.len() {
self.propagate_min(ctx, i as usize - 1, k)?;
return Ok(());
}
ctx.set_trailed(self.first[k], 0);
Ok(())
}
fn upper_limit<Ctx>(&self, ctx: &mut Ctx, k: usize) -> IntVal
where
Ctx: TrailingActions,
{
min(ctx.trailed(self.last[k]), self.vars.len() as IntVal - 1)
}
}
impl<E, I> Constraint<E> for IntValuePrecedeChainValue<I>
where
E: ReasoningEngine,
I: IntModelActions<E>,
{
fn simplify(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<SimplificationStatus, E::Conflict> {
if self.values.len() < 2 {
return Ok(SimplificationStatus::Subsumed);
}
if !self.initialized {
self.vars
.retain(|var| self.values.iter().any(|&val| var.in_domain(ctx, val)));
self.vars.shrink_to_fit();
}
self.propagate(ctx)?;
if self.vars.iter().all(|v| v.val(ctx).is_some()) {
return Ok(SimplificationStatus::Subsumed);
}
Ok(SimplificationStatus::NoFixpoint)
}
fn to_solver(&self, slv: &mut LoweringContext<'_>) -> Result<(), LoweringError> {
let vars: Vec<_> = self
.vars
.iter()
.map(|v| slv.solver_view(v.clone().into()))
.collect();
IntValuePrecedeChainValue::post(slv, self.values.clone(), vars);
Ok(())
}
}
impl<E, I> Propagator<E> for IntValuePrecedeChainValue<I>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
fn initialize(&mut self, ctx: &mut E::InitializationContext<'_>) {
ctx.set_priority(PriorityLevel::Low);
for v in &self.vars {
v.enqueue_when(ctx, IntPropCond::Domain);
}
}
#[tracing::instrument(
name = "int_value_precede_chain_value",
target = "solver",
level = "trace",
skip(self, ctx)
)]
fn propagate(&mut self, ctx: &mut E::PropagationContext<'_>) -> Result<(), E::Conflict> {
if !self.initialized {
return self.initial_propagation(ctx);
}
for (k, &t) in self.first.iter().enumerate().skip(1) {
let i = ctx.trailed(t);
if ctx.trailed(self.first_val[i as usize]) == k as IntVal
&& !self.vars[i as usize].in_domain(ctx, self.values[k - 1])
{
self.repair_upper(ctx, k)?;
}
}
let mut i = self.vars.len();
let mut k = ctx.trailed(self.max_last) as usize;
while i > 0 {
i -= 1;
if k > 0 && ctx.trailed(self.last[k - 1]) == i as IntVal {
k -= 1;
};
let li = self.lowest_index(ctx, i);
if li.is_err() {
return Ok(());
}
if let Ok(Some(lb)) = li {
if lb > k {
ctx.set_trailed(self.last[lb], i as IntVal);
if lb as IntVal > ctx.trailed(self.max_last) {
ctx.set_trailed(self.max_last, lb as IntVal);
}
(i, k) = self.repair_lower(ctx, lb)?;
continue;
}
}
if ctx.trailed(self.last[k]) == i as IntVal
&& !self.vars[i].in_domain(ctx, self.values[k - 1])
{
(i, k) = self.repair_lower(ctx, k)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::cmp::max;
use expect_test::expect;
use tracing_test::traced_test;
use crate::{
IntSet, IntVal,
constraints::int_value_precede::{IntSeqPrecedeChainBounds, IntValuePrecedeChainValue},
solver::{
LiteralStrategy, Solver,
Value::{self, Int},
},
};
#[test]
#[traced_test]
fn test_seq_precede_chain_paper() {
let mut slv = Solver::default();
let x1 = slv
.new_int_decision(-1..=1)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x2 = slv
.new_int_decision(IntSet::from_iter([-1..=1, 5..=5]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x3 = slv
.new_int_decision(IntSet::from_iter([-1..=0, 3..=3]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x4 = slv
.new_int_decision(IntSet::from_iter([0..=2, 4..=4]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x5 = slv
.new_int_decision(IntSet::from_iter([0..=1, 3..=3]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x6 = slv
.new_int_decision(IntSet::from_iter([1..=1, 3..=3]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x7 = slv
.new_int_decision(2..=5)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x8 = slv
.new_int_decision(4..=5)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x9 = slv
.new_int_decision(0..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntSeqPrecedeChainBounds::post(&mut slv, vec![x1, x2, x3, x4, x5, x6, x7, x8, x9]);
slv.assert_all_solutions(
&[x1, x2, x3, x4, x5, x6, x7, x8, x9],
valid_sequence_precede,
);
}
#[test]
#[traced_test]
fn test_seq_precede_chain_simple() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(0..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x1 = slv
.new_int_decision(0..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntSeqPrecedeChainBounds::post(&mut slv, vec![x0, x1]);
slv.expect_solutions(
&[x0, x1],
expect![[r#"
0, 0
0, 1
1, 0
1, 1
1, 2"#]],
);
}
#[test]
#[traced_test]
fn test_seq_precede_chain_single_var() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(-1..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntSeqPrecedeChainBounds::post(&mut slv, vec![x0]);
slv.expect_solutions(
&[x0],
expect![[r#"
-1
0
1"#]],
);
}
#[test]
#[traced_test]
fn test_seq_precede_chain_unrestricted() {
let mut slv = Solver::default();
let x1 = slv
.new_int_decision(-1..=4)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x2 = slv
.new_int_decision(-1..=4)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x3 = slv
.new_int_decision(-1..=4)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x4 = slv
.new_int_decision(-1..=4)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntSeqPrecedeChainBounds::post(&mut slv, vec![x1, x2, x3, x4]);
slv.assert_all_solutions(&[x1, x2, x3, x4], valid_sequence_precede);
}
#[test]
#[traced_test]
fn test_val_precede_chain_all_enforced() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(1..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x1 = slv
.new_int_decision(1..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntValuePrecedeChainValue::post(&mut slv, vec![3, 2, 1], vec![x0, x1]);
slv.expect_solutions(
&[x0, x1],
expect![[r#"
3, 2
3, 3"#]],
);
}
#[test]
#[traced_test]
fn test_val_precede_chain_simple() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(0..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x1 = slv
.new_int_decision(0..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntValuePrecedeChainValue::post(&mut slv, vec![1, 3], vec![x0, x1]);
slv.expect_solutions(
&[x0, x1],
expect![[r#"
0, 0
0, 1
0, 2
1, 0
1, 1
1, 2
1, 3
2, 0
2, 1
2, 2"#]],
);
}
#[test]
#[traced_test]
fn test_val_precede_chain_single_var() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(0..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntValuePrecedeChainValue::post(&mut slv, vec![2, 1], vec![x0]);
slv.expect_solutions(
&[x0],
expect![[r#"
0
2
3"#]],
);
}
#[test]
#[traced_test]
fn test_value_precede_chain_complex() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(IntSet::from_iter([0..=0, 2..=2]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x1 = slv
.new_int_decision(2..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x2 = slv
.new_int_decision(IntSet::from_iter([-3..=-3, 1..=1]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x3 = slv
.new_int_decision(IntSet::from_iter([-2..=0, 2..=2]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x4 = slv
.new_int_decision(0..=2)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x5 = slv
.new_int_decision(1..=2)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x6 = slv
.new_int_decision(IntSet::from_iter([-2..=-1, 1..=1]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x7 = slv
.new_int_decision(IntSet::from_iter([-1..=-1, 3..=3]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x8 = slv
.new_int_decision(1..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntValuePrecedeChainValue::post(
&mut slv,
vec![2, -2, 1, -1],
vec![x0, x1, x2, x3, x4, x5, x6, x7, x8],
);
slv.assert_all_solutions(
&[x0, x1, x2, x3, x4, x5, x6, x7, x8],
valid_value_precede(vec![2, -2, 1, -1]),
);
}
#[test]
#[traced_test]
fn test_value_precede_chain_holes() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(IntSet::from_iter([0..=1, 3..=3]))
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntValuePrecedeChainValue::post(&mut slv, vec![1, 3], vec![x0]);
slv.expect_solutions(
&[x0],
expect![[r#"
0
1"#]],
);
}
#[test]
#[traced_test]
fn test_value_precede_chain_out_of_bounds() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(0..=1)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x1 = slv
.new_int_decision(0..=1)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x2 = slv
.new_int_decision(0..=1)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntValuePrecedeChainValue::post(&mut slv, vec![1, 3], vec![x0, x1, x2]);
slv.assert_all_solutions(&[x0, x1, x2], valid_value_precede(vec![1, 3]));
}
#[test]
#[traced_test]
fn test_value_precede_chain_simple() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(0..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x1 = slv
.new_int_decision(0..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x2 = slv
.new_int_decision(0..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntValuePrecedeChainValue::post(&mut slv, vec![1, 2], vec![x0, x1, x2]);
slv.assert_all_solutions(&[x0, x1, x2], valid_value_precede(vec![1, 2]));
}
#[test]
#[traced_test]
fn test_value_precede_chain_unrestricted() {
let mut slv = Solver::default();
let x0 = slv
.new_int_decision(-2..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x1 = slv
.new_int_decision(-3..=2)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x2 = slv
.new_int_decision(-2..=3)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
let x3 = slv
.new_int_decision(-3..=2)
.order_literals(LiteralStrategy::Eager)
.direct_literals(LiteralStrategy::Eager)
.view();
IntValuePrecedeChainValue::post(&mut slv, vec![2, -2, 1, -1], vec![x0, x1, x2, x3]);
slv.assert_all_solutions(&[x0, x1, x2, x3], valid_value_precede(vec![2, -2, 1, -1]));
}
fn valid_sequence_precede(sol: &[Value]) -> bool {
sol.iter()
.map(|v| {
let Int(val) = *v else { return None };
Some(val)
})
.try_fold(0, |u, val| match (u, val) {
(uv, Some(val)) => {
if val <= uv + 1 {
Some(max(uv, val))
} else {
None
}
}
_ => None,
})
.is_some()
}
fn valid_value_precede(values: Vec<IntVal>) -> impl Fn(&[Value]) -> bool {
move |sol| {
let mut cur_index = 0;
for v in sol.iter() {
if let Int(val) = *v {
for &forbidden in values.iter().skip(cur_index + 1) {
if forbidden == val {
return false;
}
}
if cur_index < values.len() && val == values[cur_index] {
cur_index += 1;
}
} else {
return false;
}
}
true
}
}
}