pub mod all_different;
pub mod bucket_load;
pub mod cardinality;
pub mod cumulative;
pub mod domain_filter;
pub mod equal;
pub mod less_than;
pub mod minimum_distance;
pub mod no_overlap;
pub mod not_equal;
pub mod optional;
pub mod periodic_values;
pub mod precedence;
pub use all_different::AllDifferent;
pub use bucket_load::{BucketBlockPattern, BucketRange, BucketedTask, MaximumBucketLoad};
pub use cardinality::{AtLeast, AtMost, ExactlyOne};
pub use cumulative::{Cumulative, TaskDemand};
pub use domain_filter::{AllowedValues, ForbiddenValues};
pub use equal::Equal;
pub use less_than::LessThanOrEqual;
pub use minimum_distance::MinimumDistance;
pub use no_overlap::NoOverlap;
pub use not_equal::NotEqual;
pub use optional::Optional;
pub use periodic_values::PeriodicValues;
pub use precedence::Precedence;
use crate::model::domain::{Domain, TrailedDomains};
use crate::model::variable::VariableId;
use std::collections::HashMap;
use std::fmt::Debug;
pub type Assignment = HashMap<VariableId, i64>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Explanation {
pub constraint_name: &'static str,
pub involved: Vec<VariableId>,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PropagationResult {
Success { changed: bool },
Conflict,
}
pub trait Constraint: Debug + Send + Sync {
fn name(&self) -> &str;
fn scope(&self) -> &[VariableId];
fn is_satisfied(&self, assignment: &HashMap<VariableId, i64>) -> bool;
fn violations(&self, assignment: &HashMap<VariableId, i64>) -> u32 {
u32::from(!self.is_satisfied(assignment))
}
fn explain(&self, assignment: &Assignment) -> Option<Explanation> {
let _ = assignment;
None
}
fn propagate(&self, domains: &mut TrailedDomains) -> PropagationResult;
fn validate(&self) -> Result<(), String> {
Ok(())
}
fn is_satisfiable(
&self,
domains: &HashMap<VariableId, Domain>,
assignment: &HashMap<VariableId, i64>,
) -> bool {
let _ = domains;
self.is_satisfied(assignment)
}
}
pub(crate) fn compare_assigned(
assignment: &HashMap<VariableId, i64>,
v1: VariableId,
v2: VariableId,
cmp: impl FnOnce(i64, i64) -> bool,
) -> bool {
match (assignment.get(&v1), assignment.get(&v2)) {
(Some(&val1), Some(&val2)) => cmp(val1, val2),
_ => true,
}
}
pub(crate) fn require_bounds(
domains: &HashMap<VariableId, Domain>,
var: VariableId,
) -> Result<(i64, i64), PropagationResult> {
match domains.get(&var) {
Some(d) => match (d.min(), d.max()) {
(Some(min), Some(max)) => Ok((min, max)),
_ => Err(PropagationResult::Conflict),
},
None => Err(PropagationResult::Success { changed: false }),
}
}
pub(crate) fn domain_bounds(
domains: &HashMap<VariableId, Domain>,
var: VariableId,
) -> Option<(i64, i64)> {
let d = domains.get(&var)?;
Some((d.min()?, d.max()?))
}
pub(crate) fn prune(
domains: &mut TrailedDomains,
changed: &mut bool,
var: VariableId,
narrow: impl FnOnce(&mut Domain) -> bool,
) -> Option<PropagationResult> {
if domains.mutate(var, narrow)? {
*changed = true;
}
if domains.get(&var)?.is_empty() {
return Some(PropagationResult::Conflict);
}
None
}
pub(crate) fn duration_as_i64(duration: u64) -> i64 {
i64::try_from(duration).unwrap_or(i64::MAX)
}
pub(crate) fn energetic_overload(tasks: &[(i64, i64, i64)], capacity: u32) -> bool {
let capacity = i64::from(capacity);
for &(a, _, _) in tasks {
for &(_, b, _) in tasks {
if a >= b {
continue;
}
let window = b - a;
let energy_sum = tasks
.iter()
.filter(|&&(est, lct, _)| est >= a && lct <= b)
.fold(0i64, |acc, &(_, _, energy)| acc.saturating_add(energy));
if energy_sum > capacity.saturating_mul(window) {
return true;
}
}
}
false
}