mod bounds;
mod domain;
mod value;
use itertools::{Either, Itertools};
use tracing::warn;
pub use crate::constraints::int_unique::{
bounds::IntUniqueBounds, domain::IntUniqueDomain, value::IntUniqueValue,
};
use crate::{
IntVal,
actions::{IntEvent, IntInspectionActions, ReasoningEngine},
constraints::{
Constraint, IntModelActions, IntSolverActions, Propagator, SimplificationStatus,
},
lower::{LoweringContext, LoweringError},
model::View,
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct IntUnique {
pub(crate) bounds_prop: IntUniqueBounds<View<IntVal>>,
pub(crate) value_prop: IntUniqueValue<View<IntVal>>,
pub(crate) bounds_propagation: Option<bool>,
pub(crate) value_propagation: Option<bool>,
pub(crate) domain_propagation: Option<bool>,
}
impl IntUnique {
pub fn bounds_propagation(&self) -> bool {
self.bounds_propagation.unwrap_or(true)
}
pub fn domain_propagation(&self) -> bool {
self.domain_propagation.unwrap_or(false)
}
pub fn value_propagation(&self) -> bool {
self.value_propagation.unwrap_or(false)
}
}
impl<E> Constraint<E> for IntUnique
where
E: ReasoningEngine,
View<IntVal>: IntModelActions<E>,
{
fn simplify(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<SimplificationStatus, E::Conflict> {
self.propagate(ctx)?;
Ok(SimplificationStatus::NoFixpoint)
}
fn to_solver(&self, slv: &mut LoweringContext<'_>) -> Result<(), LoweringError> {
let (_vals, vars): (Vec<_>, Vec<_>) = self.bounds_prop.vars.iter().partition_map(|&var| {
let var = slv.solver_view(var);
if let Some(val) = var.val(slv) {
Either::Left(val)
} else {
Either::Right(var)
}
});
debug_assert!(_vals.iter().unique().collect_vec().len() == _vals.len());
debug_assert!(
_vals
.iter()
.all(|&val| vars.iter().all(|var| !var.in_domain(slv, val)))
);
if vars.len() <= 1 {
return Ok(());
}
let value_propagation = self.value_propagation();
if value_propagation {
IntUniqueValue::post(slv, vars.clone());
}
let domain_propagation = self.domain_propagation();
let mut bounds_propagation = self.bounds_propagation();
if !value_propagation && !bounds_propagation && !domain_propagation {
warn!(
"all propagation algorithms are disabled for `int_unique` constraint, override with bounds propagation to ensure consistency"
);
bounds_propagation = true;
}
if bounds_propagation {
IntUniqueBounds::post(slv, vars.clone());
}
if domain_propagation {
IntUniqueDomain::post(slv, vars);
}
Ok(())
}
}
impl<E> Propagator<E> for IntUnique
where
E: ReasoningEngine,
View<IntVal>: IntSolverActions<E>,
{
fn advise_of_backtrack(&mut self, ctx: &mut E::NotificationContext<'_>) {
self.value_prop.advise_of_backtrack(ctx);
}
fn advise_of_int_change(
&mut self,
ctx: &mut E::NotificationContext<'_>,
data: u64,
event: IntEvent,
) -> bool {
match event {
IntEvent::Fixed => self.value_prop.advise_of_int_change(ctx, data, event),
IntEvent::Bounds => self.bounds_prop.advise_of_int_change(ctx, data, event),
_ => unreachable!("IntUnique should only be advised of Fixed and Bounds events"),
}
}
fn initialize(&mut self, ctx: &mut E::InitializationContext<'_>) {
self.value_prop.initialize(ctx);
self.bounds_prop.initialize(ctx);
}
fn propagate(&mut self, ctx: &mut E::PropagationContext<'_>) -> Result<(), E::Conflict> {
if self.value_prop.has_pending_actions() {
self.value_prop.propagate(ctx)?;
}
self.bounds_prop.propagate(ctx)
}
}
#[cfg(test)]
mod tests {
use tracing_test::traced_test;
use crate::{IntSet, model::Model};
#[test]
#[traced_test]
fn test_gapped_domain_regression() {
let mut prb = Model::default();
let prev: Vec<_> = [
IntSet::from_iter([15..=15, 20..=20]),
(20..=20).into(),
IntSet::from_iter([15..=15, 20..=20]),
]
.into_iter()
.map(|domain| prb.new_int_decision(domain))
.collect();
assert!(prb.unique(prev.iter().copied()).post().is_err());
}
}