use std::fmt::Debug;
use solverforge_core::domain::PlanningSolution;
use solverforge_scoring::Director;
pub trait ScoreBounder<S: PlanningSolution, D: Director<S>>: Send + Debug {
fn calculate_optimistic_bound(&self, score_director: &D) -> Option<S::Score>;
fn calculate_pessimistic_bound(&self, score_director: &D) -> Option<S::Score> {
let _ = score_director;
None
}
}
#[derive(Debug, Clone, Default)]
pub struct SoftScoreBounder;
impl SoftScoreBounder {
pub fn new() -> Self {
Self
}
}
impl<S: PlanningSolution, D: Director<S>> ScoreBounder<S, D> for SoftScoreBounder {
fn calculate_optimistic_bound(&self, _score_director: &D) -> Option<S::Score> {
None
}
}
#[derive(Clone)]
pub struct FixedOffsetBounder<S: PlanningSolution> {
max_improvement_per_entity: S::Score,
}
impl<S: PlanningSolution> std::fmt::Debug for FixedOffsetBounder<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FixedOffsetBounder").finish()
}
}
impl<S: PlanningSolution> FixedOffsetBounder<S> {
pub fn new(max_improvement_per_entity: S::Score) -> Self {
Self {
max_improvement_per_entity,
}
}
}
impl<S: PlanningSolution, D: Director<S>> ScoreBounder<S, D> for FixedOffsetBounder<S>
where
S::Score: Clone + std::ops::Add<Output = S::Score> + std::ops::Mul<i32, Output = S::Score>,
{
fn calculate_optimistic_bound(&self, score_director: &D) -> Option<S::Score> {
let total = score_director.total_entity_count()?;
let current_score = score_director.working_solution().score()?;
let bound = current_score + self.max_improvement_per_entity * (total as i32);
Some(bound)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BounderType {
#[default]
None,
Simple,
FixedOffset,
}
impl std::fmt::Display for BounderType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BounderType::None => write!(f, "None"),
BounderType::Simple => write!(f, "Simple"),
BounderType::FixedOffset => write!(f, "FixedOffset"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_bounder_returns_none() {
let bounder = SoftScoreBounder::new();
assert!(format!("{:?}", bounder).contains("SoftScoreBounder"));
}
#[test]
fn test_bounder_type_display() {
assert_eq!(format!("{}", BounderType::None), "None");
assert_eq!(format!("{}", BounderType::Simple), "Simple");
assert_eq!(format!("{}", BounderType::FixedOffset), "FixedOffset");
}
#[test]
fn test_bounder_type_default() {
assert_eq!(BounderType::default(), BounderType::None);
}
}