use crate::model::domain::Domain;
use crate::model::variable::VariableId;
use crate::propagation::graph::ConstraintGraph;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HardSoftScore {
pub hard: i64,
pub soft: i64,
}
impl HardSoftScore {
pub fn new(hard: i64, soft: i64) -> Self {
Self { hard, soft }
}
pub fn feasible(soft: i64) -> Self {
Self { hard: 0, soft }
}
pub fn infeasible(hard: i64) -> Self {
Self { hard, soft: 0 }
}
#[inline]
pub fn is_feasible(&self) -> bool {
self.hard >= 0
}
}
impl Ord for HardSoftScore {
fn cmp(&self, other: &Self) -> Ordering {
match self.hard.cmp(&other.hard) {
Ordering::Equal => self.soft.cmp(&other.soft),
ord => ord,
}
}
}
impl PartialOrd for HardSoftScore {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for HardSoftScore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_feasible() {
write!(f, "Feasible({})", self.soft)
} else {
write!(f, "Infeasible(hard={}, soft={})", self.hard, self.soft)
}
}
}
pub trait Objective: Debug + Send + Sync {
fn name(&self) -> &str;
fn scope(&self) -> &[VariableId];
fn evaluate(&self, assignment: &HashMap<VariableId, i64>) -> i64;
fn optimistic_bound(&self, domains: &HashMap<VariableId, Domain>) -> i64;
}
#[derive(Debug, Clone)]
pub struct WeightedSum {
vars: Vec<VariableId>,
weight: i64,
}
impl WeightedSum {
pub fn new(vars: impl IntoIterator<Item = VariableId>, weight: i64) -> Self {
Self {
vars: vars.into_iter().collect(),
weight,
}
}
}
impl Objective for WeightedSum {
fn name(&self) -> &str {
"WeightedSum"
}
fn scope(&self) -> &[VariableId] {
&self.vars
}
fn evaluate(&self, assignment: &HashMap<VariableId, i64>) -> i64 {
let sum = self
.vars
.iter()
.filter_map(|v| assignment.get(v))
.fold(0i64, |acc, &v| acc.saturating_add(v));
self.weight.saturating_mul(sum)
}
fn optimistic_bound(&self, domains: &HashMap<VariableId, Domain>) -> i64 {
self.vars
.iter()
.map(|v| match domains.get(v) {
Some(d) if !d.is_empty() => {
let extreme = if self.weight >= 0 { d.max() } else { d.min() };
extreme.unwrap_or(0)
}
_ => 0,
})
.fold(0i64, |acc, v| {
acc.saturating_add(self.weight.saturating_mul(v))
})
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ScoreCalculator;
impl ScoreCalculator {
pub fn calculate_score(
&self,
graph: &ConstraintGraph,
assignment: &HashMap<VariableId, i64>,
) -> HardSoftScore {
let mut hard_violations: i64 = 0;
for constraint in graph.constraints() {
if !constraint.is_satisfied(assignment) {
hard_violations -= 1;
}
}
let soft_score: i64 = graph
.objectives()
.iter()
.map(|o| o.evaluate(assignment))
.sum();
HardSoftScore::new(hard_violations, soft_score)
}
pub fn optimistic_score(
&self,
graph: &ConstraintGraph,
domains: &HashMap<VariableId, Domain>,
assignment: &HashMap<VariableId, i64>,
) -> HardSoftScore {
let hard: i64 = graph
.constraints()
.iter()
.map(|c| {
if c.is_satisfiable(domains, assignment) {
0
} else {
-1
}
})
.sum();
let soft_bound: i64 = graph
.objectives()
.iter()
.map(|o| o.optimistic_bound(domains))
.sum();
HardSoftScore::new(hard, soft_bound)
}
pub fn update_incremental_score(
&self,
graph: &ConstraintGraph,
old_assignment: &HashMap<VariableId, i64>,
new_assignment: &HashMap<VariableId, i64>,
changed_var: VariableId,
current_score: HardSoftScore,
) -> HardSoftScore {
let mut hard_delta: i64 = 0;
for &cid in graph.constraints_for_variable(changed_var) {
if let Some(constraint) = graph.get_constraint(cid) {
let was_satisfied = constraint.is_satisfied(old_assignment);
let is_satisfied = constraint.is_satisfied(new_assignment);
match (was_satisfied, is_satisfied) {
(false, true) => hard_delta += 1, (true, false) => hard_delta -= 1, _ => {}
}
}
}
let mut soft_delta: i64 = 0;
for objective in graph.objectives() {
if objective.scope().contains(&changed_var) {
let delta = objective
.evaluate(new_assignment)
.saturating_sub(objective.evaluate(old_assignment));
soft_delta = soft_delta.saturating_add(delta);
}
}
HardSoftScore::new(
current_score.hard.saturating_add(hard_delta),
current_score.soft.saturating_add(soft_delta),
)
}
}