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