solverforge_solver/termination/
score_calculation_count.rs1use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6
7use super::Termination;
8use crate::scope::SolverScope;
9
10#[derive(Clone)]
34pub struct ScoreCalculationCountTermination<S: PlanningSolution> {
35 score_calculation_count_limit: u64,
37 _phantom: std::marker::PhantomData<fn() -> S>,
38}
39
40impl<S: PlanningSolution> Debug for ScoreCalculationCountTermination<S> {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("ScoreCalculationCountTermination")
43 .field(
44 "score_calculation_count_limit",
45 &self.score_calculation_count_limit,
46 )
47 .finish()
48 }
49}
50
51impl<S: PlanningSolution> ScoreCalculationCountTermination<S> {
52 pub fn new(score_calculation_count_limit: u64) -> Self {
57 Self {
58 score_calculation_count_limit,
59 _phantom: std::marker::PhantomData,
60 }
61 }
62}
63
64impl<S: PlanningSolution> Termination<S> for ScoreCalculationCountTermination<S> {
65 fn is_terminated(&self, solver_scope: &SolverScope<S>) -> bool {
66 if let Some(stats) = solver_scope.statistics() {
67 stats.current_score_calculations() >= self.score_calculation_count_limit
68 } else {
69 false }
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use crate::statistics::StatisticsCollector;
78 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
79 use solverforge_core::score::SimpleScore;
80 use solverforge_scoring::SimpleScoreDirector;
81 use std::any::TypeId;
82 use std::sync::Arc;
83
84 #[derive(Clone, Debug)]
85 struct Entity {}
86
87 #[derive(Clone, Debug)]
88 struct TestSolution {
89 entities: Vec<Entity>,
90 score: Option<SimpleScore>,
91 }
92
93 impl PlanningSolution for TestSolution {
94 type Score = SimpleScore;
95 fn score(&self) -> Option<Self::Score> {
96 self.score
97 }
98 fn set_score(&mut self, score: Option<Self::Score>) {
99 self.score = score;
100 }
101 }
102
103 fn get_entities(s: &TestSolution) -> &Vec<Entity> {
104 &s.entities
105 }
106 fn get_entities_mut(s: &mut TestSolution) -> &mut Vec<Entity> {
107 &mut s.entities
108 }
109
110 fn create_scope_with_stats() -> SolverScope<TestSolution> {
111 let solution = TestSolution {
112 entities: vec![],
113 score: None,
114 };
115 let extractor = Box::new(TypedEntityExtractor::new(
116 "Entity",
117 "entities",
118 get_entities,
119 get_entities_mut,
120 ));
121 let entity_desc = EntityDescriptor::new("Entity", TypeId::of::<Entity>(), "entities")
122 .with_extractor(extractor);
123 let descriptor = SolutionDescriptor::new("TestSolution", TypeId::of::<TestSolution>())
124 .with_entity(entity_desc);
125 let director =
126 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0));
127 let collector = Arc::new(StatisticsCollector::<SimpleScore>::new());
128 SolverScope::new(Box::new(director)).with_statistics(collector)
129 }
130
131 #[test]
132 fn test_not_terminated_initially() {
133 let scope = create_scope_with_stats();
134 let termination = ScoreCalculationCountTermination::<TestSolution>::new(100);
135 assert!(!termination.is_terminated(&scope));
136 }
137
138 #[test]
139 fn test_terminates_at_limit() {
140 let scope = create_scope_with_stats();
141 let stats = scope.statistics().unwrap().clone();
142
143 for _ in 0..99 {
145 stats.record_score_calculation();
146 }
147 let termination = ScoreCalculationCountTermination::<TestSolution>::new(100);
148 assert!(!termination.is_terminated(&scope));
149
150 stats.record_score_calculation();
152 assert!(termination.is_terminated(&scope));
153 }
154
155 #[test]
156 fn test_no_stats_never_terminates() {
157 let solution = TestSolution {
158 entities: vec![],
159 score: None,
160 };
161 let extractor = Box::new(TypedEntityExtractor::new(
162 "Entity",
163 "entities",
164 get_entities,
165 get_entities_mut,
166 ));
167 let entity_desc = EntityDescriptor::new("Entity", TypeId::of::<Entity>(), "entities")
168 .with_extractor(extractor);
169 let descriptor = SolutionDescriptor::new("TestSolution", TypeId::of::<TestSolution>())
170 .with_entity(entity_desc);
171 let director =
172 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0));
173 let scope = SolverScope::new(Box::new(director));
174
175 let termination = ScoreCalculationCountTermination::<TestSolution>::new(1);
176 assert!(!termination.is_terminated(&scope));
178 }
179}