solverforge_solver/phase/exhaustive/
phase.rs1use std::collections::BinaryHeap;
4use std::fmt::Debug;
5
6use solverforge_core::domain::PlanningSolution;
7use solverforge_scoring::Director;
8
9use crate::phase::Phase;
10use crate::scope::BestSolutionCallback;
11use crate::scope::{PhaseScope, SolverScope};
12
13use super::config::ExhaustiveSearchConfig;
14use super::decider::ExhaustiveSearchDecider;
15use super::exploration_type::ExplorationType;
16use super::node::ExhaustiveSearchNode;
17use super::priority_node::PriorityNode;
18
19pub struct ExhaustiveSearchPhase<Dec> {
56 decider: Dec,
58 config: ExhaustiveSearchConfig,
60}
61
62impl<Dec: Debug> Debug for ExhaustiveSearchPhase<Dec> {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("ExhaustiveSearchPhase")
65 .field("decider", &self.decider)
66 .field("config", &self.config)
67 .finish()
68 }
69}
70
71impl<Dec> ExhaustiveSearchPhase<Dec> {
72 pub fn new(decider: Dec, config: ExhaustiveSearchConfig) -> Self {
74 Self { decider, config }
75 }
76
77 pub fn depth_first(decider: Dec) -> Self {
79 Self::new(
80 decider,
81 ExhaustiveSearchConfig {
82 exploration_type: ExplorationType::DepthFirst,
83 ..Default::default()
84 },
85 )
86 }
87
88 pub fn breadth_first(decider: Dec) -> Self {
90 Self::new(
91 decider,
92 ExhaustiveSearchConfig {
93 exploration_type: ExplorationType::BreadthFirst,
94 ..Default::default()
95 },
96 )
97 }
98
99 pub fn score_first(decider: Dec) -> Self {
101 Self::new(
102 decider,
103 ExhaustiveSearchConfig {
104 exploration_type: ExplorationType::ScoreFirst,
105 ..Default::default()
106 },
107 )
108 }
109
110 pub fn phase_type_name(&self) -> &'static str {
112 "ExhaustiveSearch"
113 }
114}
115
116impl<S, D, BestCb, Dec> Phase<S, D, BestCb> for ExhaustiveSearchPhase<Dec>
117where
118 S: PlanningSolution,
119 D: Director<S>,
120 BestCb: BestSolutionCallback<S>,
121 Dec: ExhaustiveSearchDecider<S, D>,
122{
123 fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
124 let mut phase_scope = PhaseScope::new(solver_scope, 0);
125
126 let total_entities = self.decider.total_entities(phase_scope.score_director());
128 if total_entities == 0 {
129 return;
130 }
131
132 let initial_score = phase_scope.calculate_score();
134
135 let root = ExhaustiveSearchNode::root(initial_score);
137
138 let mut best_score: Option<S::Score> = None;
140 let mut nodes_explored: u64 = 0;
141
142 let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
144 frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
145
146 let mut all_nodes: Vec<ExhaustiveSearchNode<S>> = Vec::new();
148
149 while let Some(priority_node) = frontier.pop() {
150 let node = priority_node.node;
151 let node_index = all_nodes.len();
152
153 if let Some(limit) = self.config.node_limit {
155 if nodes_explored >= limit {
156 break;
157 }
158 }
159
160 if let Some(limit) = self.config.depth_limit {
162 if node.depth() >= limit {
163 continue;
164 }
165 }
166
167 if self.config.enable_pruning {
169 if let Some(ref best) = best_score {
170 if node.can_prune(best) {
171 continue;
172 }
173 }
174 }
175
176 nodes_explored += 1;
177
178 if node.is_leaf(total_entities) {
180 let is_better = match &best_score {
181 None => true,
182 Some(best) => node.score() > best,
183 };
184
185 if is_better {
186 best_score = Some(*node.score());
187 phase_scope.update_best_solution();
188 }
189
190 all_nodes.push(node);
191 continue;
192 }
193
194 let children = self
196 .decider
197 .expand(node_index, &node, phase_scope.score_director_mut());
198
199 all_nodes.push(node);
201
202 for child in children {
204 if self.config.enable_pruning {
206 if let Some(ref best) = best_score {
207 if child.can_prune(best) {
208 continue;
209 }
210 }
211 }
212
213 let child_index = all_nodes.len() + frontier.len();
214 frontier.push(PriorityNode::new(
215 child_index,
216 child,
217 self.config.exploration_type,
218 ));
219 }
220 }
221 }
222
223 fn phase_type_name(&self) -> &'static str {
224 "ExhaustiveSearch"
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use solverforge_core::domain::PlanningSolution;
231 use solverforge_core::score::SoftScore;
232
233 use super::*;
234 use crate::phase::exhaustive::decider::SimpleDecider;
235 use crate::phase::exhaustive::exploration_type::ExplorationType;
236
237 #[derive(Clone, Debug)]
238 struct TestSolution {
239 score: Option<SoftScore>,
240 }
241
242 impl PlanningSolution for TestSolution {
243 type Score = SoftScore;
244
245 fn score(&self) -> Option<Self::Score> {
246 self.score
247 }
248
249 fn set_score(&mut self, score: Option<Self::Score>) {
250 self.score = score;
251 }
252 }
253
254 fn set_row(_s: &mut TestSolution, _idx: usize, _v: Option<i32>) {}
256
257 #[test]
258 fn test_exploration_type_display() {
259 assert_eq!(format!("{}", ExplorationType::DepthFirst), "DepthFirst");
260 assert_eq!(format!("{}", ExplorationType::BreadthFirst), "BreadthFirst");
261 assert_eq!(format!("{}", ExplorationType::ScoreFirst), "ScoreFirst");
262 assert_eq!(
263 format!("{}", ExplorationType::OptimisticBoundFirst),
264 "OptimisticBoundFirst"
265 );
266 }
267
268 #[test]
269 fn test_exploration_type_default() {
270 assert_eq!(ExplorationType::default(), ExplorationType::DepthFirst);
271 }
272
273 #[test]
274 fn test_config_default() {
275 let config = ExhaustiveSearchConfig::default();
276 assert_eq!(config.exploration_type, ExplorationType::DepthFirst);
277 assert_eq!(config.node_limit, Some(10_000));
278 assert!(config.depth_limit.is_none());
279 assert!(config.enable_pruning);
280 }
281
282 #[test]
283 fn test_phase_type_name() {
284 let decider: SimpleDecider<TestSolution, i32> =
285 SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
286 let phase = ExhaustiveSearchPhase::depth_first(decider);
287
288 assert_eq!(phase.phase_type_name(), "ExhaustiveSearch");
289 }
290
291 #[test]
292 fn test_phase_debug() {
293 let decider: SimpleDecider<TestSolution, i32> =
294 SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
295 let phase = ExhaustiveSearchPhase::depth_first(decider);
296
297 let debug = format!("{:?}", phase);
298 assert!(debug.contains("ExhaustiveSearchPhase"));
299 assert!(debug.contains("DepthFirst"));
300 }
301}