use crate::constraint::{Constraint, PropagationResult};
use crate::model::domain::{Domain, TrailedDomains};
use crate::model::variable::VariableId;
use std::collections::HashMap;
fn count_at_target(
scope: &[VariableId],
assignment: &HashMap<VariableId, i64>,
target_value: i64,
) -> usize {
scope
.iter()
.filter(|v| assignment.get(v) == Some(&target_value))
.count()
}
fn target_reachability(
scope: &[VariableId],
domains: &HashMap<VariableId, Domain>,
target_value: i64,
) -> (usize, usize) {
let mut possible = 0;
let mut fixed = 0;
for &v in scope {
if let Some(d) = domains.get(&v)
&& d.contains(target_value)
{
possible += 1;
if d.len() == 1 {
fixed += 1;
}
}
}
(possible, fixed)
}
#[derive(Debug, Clone)]
pub struct ExactlyOne {
scope: Vec<VariableId>,
target_value: i64,
}
impl ExactlyOne {
pub fn new(variables: impl IntoIterator<Item = VariableId>, target_value: i64) -> Self {
Self {
scope: variables.into_iter().collect(),
target_value,
}
}
}
impl Constraint for ExactlyOne {
fn name(&self) -> &str {
"ExactlyOne"
}
fn scope(&self) -> &[VariableId] {
&self.scope
}
fn is_satisfied(&self, assignment: &HashMap<VariableId, i64>) -> bool {
count_at_target(&self.scope, assignment, self.target_value) == 1
}
fn is_satisfiable(
&self,
domains: &HashMap<VariableId, Domain>,
_assignment: &HashMap<VariableId, i64>,
) -> bool {
let (possible, fixed) = target_reachability(&self.scope, domains, self.target_value);
fixed <= 1 && possible >= 1
}
fn propagate(&self, domains: &mut TrailedDomains) -> PropagationResult {
let mut changed = false;
let mut fixed_target_var = None;
let mut possible_count = 0;
let mut last_possible_var = None;
for &var_id in &self.scope {
if let Some(domain) = domains.get(&var_id)
&& domain.contains(self.target_value)
{
possible_count += 1;
last_possible_var = Some(var_id);
if domain.len() == 1 {
if fixed_target_var.is_some() {
return PropagationResult::Conflict;
}
fixed_target_var = Some(var_id);
}
}
}
if possible_count == 0 {
return PropagationResult::Conflict;
}
if let Some(fixed_var) = fixed_target_var {
for &var_id in &self.scope {
if var_id == fixed_var {
continue;
}
if domains
.mutate(var_id, |d| d.remove(self.target_value))
.unwrap_or(false)
{
changed = true;
}
if domains.get(&var_id).is_some_and(|d| d.is_empty()) {
return PropagationResult::Conflict;
}
}
} else if possible_count == 1 {
if let Some(only_var) = last_possible_var {
if domains
.mutate(only_var, |d| d.assign(self.target_value))
.unwrap_or(false)
{
changed = true;
}
if domains.get(&only_var).is_some_and(|d| d.is_empty()) {
return PropagationResult::Conflict;
}
}
}
PropagationResult::Success { changed }
}
}
#[derive(Debug, Clone)]
pub struct AtMost {
scope: Vec<VariableId>,
target_value: i64,
k: usize,
}
impl AtMost {
pub fn new(
k: usize,
variables: impl IntoIterator<Item = VariableId>,
target_value: i64,
) -> Self {
Self {
scope: variables.into_iter().collect(),
target_value,
k,
}
}
}
impl Constraint for AtMost {
fn name(&self) -> &str {
"AtMost"
}
fn scope(&self) -> &[VariableId] {
&self.scope
}
fn is_satisfied(&self, assignment: &HashMap<VariableId, i64>) -> bool {
count_at_target(&self.scope, assignment, self.target_value) <= self.k
}
fn propagate(&self, domains: &mut TrailedDomains) -> PropagationResult {
let mut changed = false;
let mut fixed_count = 0;
for &var_id in &self.scope {
if let Some(domain) = domains.get(&var_id)
&& domain.len() == 1
&& domain.contains(self.target_value)
{
fixed_count += 1;
}
}
if fixed_count > self.k {
return PropagationResult::Conflict;
}
if fixed_count == self.k {
for &var_id in &self.scope {
if !domains
.get(&var_id)
.is_some_and(|d| d.len() > 1 && d.contains(self.target_value))
{
continue;
}
if domains
.mutate(var_id, |d| d.remove(self.target_value))
.unwrap_or(false)
{
changed = true;
}
if domains.get(&var_id).is_some_and(|d| d.is_empty()) {
return PropagationResult::Conflict;
}
}
}
PropagationResult::Success { changed }
}
}
#[derive(Debug, Clone)]
pub struct AtLeast {
scope: Vec<VariableId>,
target_value: i64,
k: usize,
}
impl AtLeast {
pub fn new(
k: usize,
variables: impl IntoIterator<Item = VariableId>,
target_value: i64,
) -> Self {
Self {
scope: variables.into_iter().collect(),
target_value,
k,
}
}
}
impl Constraint for AtLeast {
fn name(&self) -> &str {
"AtLeast"
}
fn scope(&self) -> &[VariableId] {
&self.scope
}
fn is_satisfied(&self, assignment: &HashMap<VariableId, i64>) -> bool {
count_at_target(&self.scope, assignment, self.target_value) >= self.k
}
fn is_satisfiable(
&self,
domains: &HashMap<VariableId, Domain>,
_assignment: &HashMap<VariableId, i64>,
) -> bool {
let (possible, _fixed) = target_reachability(&self.scope, domains, self.target_value);
possible >= self.k
}
fn propagate(&self, domains: &mut TrailedDomains) -> PropagationResult {
let mut changed = false;
let mut possible_vars = Vec::new();
for &var_id in &self.scope {
if let Some(domain) = domains.get(&var_id)
&& domain.contains(self.target_value)
{
possible_vars.push(var_id);
}
}
if possible_vars.len() < self.k {
return PropagationResult::Conflict;
}
if possible_vars.len() == self.k {
for var_id in possible_vars {
if domains
.mutate(var_id, |d| d.assign(self.target_value))
.unwrap_or(false)
{
changed = true;
}
if domains.get(&var_id).is_some_and(|d| d.is_empty()) {
return PropagationResult::Conflict;
}
}
}
PropagationResult::Success { changed }
}
}