solverforge_solver/phase/exhaustive/
bounder.rs1use std::fmt::Debug;
8
9use solverforge_core::domain::PlanningSolution;
10use solverforge_scoring::Director;
11
12pub trait ScoreBounder<S: PlanningSolution, D: Director<S>>: Send + Debug {
18 fn calculate_optimistic_bound(&self, score_director: &D) -> Option<S::Score>;
28
29 fn calculate_pessimistic_bound(&self, score_director: &D) -> Option<S::Score> {
38 let _ = score_director;
40 None
41 }
42}
43
44#[derive(Debug, Clone, Default)]
50pub struct SoftScoreBounder;
51
52impl SoftScoreBounder {
53 pub fn new() -> Self {
54 Self
55 }
56}
57
58impl<S: PlanningSolution, D: Director<S>> ScoreBounder<S, D> for SoftScoreBounder {
59 fn calculate_optimistic_bound(&self, _score_director: &D) -> Option<S::Score> {
60 None
63 }
64}
65
66#[derive(Clone)]
72pub struct FixedOffsetBounder<S: PlanningSolution> {
73 max_improvement_per_entity: S::Score,
75}
76
77impl<S: PlanningSolution> std::fmt::Debug for FixedOffsetBounder<S> {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 f.debug_struct("FixedOffsetBounder").finish()
80 }
81}
82
83impl<S: PlanningSolution> FixedOffsetBounder<S> {
84 pub fn new(max_improvement_per_entity: S::Score) -> Self {
85 Self {
86 max_improvement_per_entity,
87 }
88 }
89}
90
91impl<S: PlanningSolution, D: Director<S>> ScoreBounder<S, D> for FixedOffsetBounder<S>
92where
93 S::Score: Clone + std::ops::Add<Output = S::Score> + std::ops::Mul<i32, Output = S::Score>,
94{
95 fn calculate_optimistic_bound(&self, score_director: &D) -> Option<S::Score> {
96 let total = score_director.total_entity_count()?;
98
99 let current_score = score_director.working_solution().score()?;
102
103 let bound = current_score + self.max_improvement_per_entity * (total as i32);
106
107 Some(bound)
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
113pub enum BounderType {
114 #[default]
116 None,
117 Simple,
119 FixedOffset,
121}
122
123impl std::fmt::Display for BounderType {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 match self {
126 BounderType::None => write!(f, "None"),
127 BounderType::Simple => write!(f, "Simple"),
128 BounderType::FixedOffset => write!(f, "FixedOffset"),
129 }
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_simple_bounder_returns_none() {
139 let bounder = SoftScoreBounder::new();
140 assert!(format!("{:?}", bounder).contains("SoftScoreBounder"));
143 }
144
145 #[test]
146 fn test_bounder_type_display() {
147 assert_eq!(format!("{}", BounderType::None), "None");
148 assert_eq!(format!("{}", BounderType::Simple), "Simple");
149 assert_eq!(format!("{}", BounderType::FixedOffset), "FixedOffset");
150 }
151
152 #[test]
153 fn test_bounder_type_default() {
154 assert_eq!(BounderType::default(), BounderType::None);
155 }
156}