use crate::constraint::{Assignment, Constraint};
use crate::model::domain::Domain;
use crate::model::variable::{Variable, VariableId};
use crate::score::Objective;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConstraintId(pub u32);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstraintViolation {
pub constraint_id: ConstraintId,
pub constraint_name: String,
pub involved: Vec<VariableId>,
pub message: String,
}
impl fmt::Display for ConstraintId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "c{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ModelError {
UnknownVariable {
item: String,
var: VariableId,
},
EmptyDomain {
var: VariableId,
},
DuplicateVariableId {
var: VariableId,
},
InvalidConstraint {
name: String,
reason: String,
},
}
impl fmt::Display for ModelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ModelError::UnknownVariable { item, var } => {
write!(f, "{item} references unregistered variable {var:?}")
}
ModelError::EmptyDomain { var } => write!(f, "variable {var:?} has an empty domain"),
ModelError::DuplicateVariableId { var } => {
write!(f, "variable id {var:?} was registered more than once")
}
ModelError::InvalidConstraint { name, reason } => write!(f, "{name}: {reason}"),
}
}
}
impl std::error::Error for ModelError {}
#[derive(Clone, Default)]
pub struct ConstraintGraph {
variables: HashMap<VariableId, Variable>,
domains: HashMap<VariableId, Domain>,
constraints: Vec<Arc<dyn Constraint>>,
objectives: Vec<Arc<dyn Objective>>,
var_to_constraints: HashMap<VariableId, Vec<ConstraintId>>,
duplicate_variable_ids: Vec<VariableId>,
}
impl ConstraintGraph {
pub fn new() -> Self {
Self::default()
}
pub fn add_variable(&mut self, variable: Variable, domain: Domain) {
let id = variable.id();
if self.variables.contains_key(&id) {
self.duplicate_variable_ids.push(id);
}
self.variables.insert(id, variable);
self.domains.insert(id, domain);
self.var_to_constraints.entry(id).or_default();
}
pub fn add_constraint(&mut self, constraint: Arc<dyn Constraint>) -> ConstraintId {
let cid = ConstraintId(self.constraints.len() as u32);
for &var_id in constraint.scope() {
self.var_to_constraints.entry(var_id).or_default().push(cid);
}
self.constraints.push(constraint);
cid
}
#[inline]
pub fn variables(&self) -> &HashMap<VariableId, Variable> {
&self.variables
}
#[inline]
pub fn domains(&self) -> &HashMap<VariableId, Domain> {
&self.domains
}
#[inline]
pub fn domains_mut(&mut self) -> &mut HashMap<VariableId, Domain> {
&mut self.domains
}
#[inline]
pub fn constraints(&self) -> &[Arc<dyn Constraint>] {
&self.constraints
}
pub fn constraints_for_variable(&self, var_id: VariableId) -> &[ConstraintId] {
self.var_to_constraints
.get(&var_id)
.map(|vec| vec.as_slice())
.unwrap_or(&[])
}
pub fn get_constraint(&self, id: ConstraintId) -> Option<&Arc<dyn Constraint>> {
self.constraints.get(id.0 as usize)
}
pub fn add_objective(&mut self, objective: Arc<dyn Objective>) {
self.objectives.push(objective);
}
#[inline]
pub fn objectives(&self) -> &[Arc<dyn Objective>] {
&self.objectives
}
pub fn validate(&self) -> Result<(), Vec<ModelError>> {
let mut errors = Vec::new();
for &var in &self.duplicate_variable_ids {
errors.push(ModelError::DuplicateVariableId { var });
}
for (&var, domain) in &self.domains {
if domain.is_empty() {
errors.push(ModelError::EmptyDomain { var });
}
}
for constraint in &self.constraints {
for &var in constraint.scope() {
if !self.variables.contains_key(&var) {
errors.push(ModelError::UnknownVariable {
item: constraint.name().to_string(),
var,
});
}
}
if let Err(reason) = constraint.validate() {
errors.push(ModelError::InvalidConstraint {
name: constraint.name().to_string(),
reason,
});
}
}
for objective in &self.objectives {
for &var in objective.scope() {
if !self.variables.contains_key(&var) {
errors.push(ModelError::UnknownVariable {
item: objective.name().to_string(),
var,
});
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn finalize(self) -> Result<ValidatedGraph, Vec<ModelError>> {
self.validate()?;
Ok(ValidatedGraph(self))
}
}
impl fmt::Debug for ConstraintGraph {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ConstraintGraph")
.field("num_variables", &self.variables.len())
.field("num_constraints", &self.constraints.len())
.finish()
}
}
#[derive(Debug, Clone)]
pub struct ValidatedGraph(ConstraintGraph);
impl ValidatedGraph {
#[inline]
pub fn graph(&self) -> &ConstraintGraph {
&self.0
}
pub fn check_incremental(
&self,
committed: &Assignment,
proposed: &[(VariableId, i64)],
) -> Vec<ConstraintViolation> {
let mut assignment = committed.clone();
let mut affected = std::collections::BTreeSet::new();
for &(variable, value) in proposed {
assignment.insert(variable, value);
affected.extend(self.constraints_for_variable(variable));
}
affected
.into_iter()
.filter_map(|constraint_id| {
let constraint = self.get_constraint(constraint_id)?;
if constraint.is_satisfied(&assignment) {
return None;
}
let explanation = constraint.explain(&assignment);
Some(ConstraintViolation {
constraint_id,
constraint_name: explanation.as_ref().map_or_else(
|| constraint.name().to_string(),
|e| e.constraint_name.into(),
),
involved: explanation
.as_ref()
.map_or_else(|| constraint.scope().to_vec(), |e| e.involved.clone()),
message: explanation.map_or_else(
|| format!("{} constraint is violated", constraint.name()),
|e| e.message,
),
})
})
.collect()
}
pub(crate) fn assume_valid(graph: ConstraintGraph) -> Self {
Self(graph)
}
}
impl std::ops::Deref for ValidatedGraph {
type Target = ConstraintGraph;
fn deref(&self) -> &ConstraintGraph {
&self.0
}
}