solverforge_solver/phase/exhaustive/
mod.rs1mod bounder;
15mod decider;
16mod node;
17
18use std::collections::BinaryHeap;
19use std::fmt::Debug;
20
21use solverforge_core::domain::PlanningSolution;
22
23use crate::phase::Phase;
24use crate::scope::{PhaseScope, SolverScope};
25
26pub use bounder::{BounderType, FixedOffsetBounder, ScoreBounder, SimpleScoreBounder};
27pub use decider::{ExhaustiveSearchDecider, SimpleDecider};
28pub use node::{ExhaustiveSearchNode, MoveSequence};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub enum ExplorationType {
33 #[default]
36 DepthFirst,
37
38 BreadthFirst,
41
42 ScoreFirst,
45
46 OptimisticBoundFirst,
49}
50
51impl std::fmt::Display for ExplorationType {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 ExplorationType::DepthFirst => write!(f, "DepthFirst"),
55 ExplorationType::BreadthFirst => write!(f, "BreadthFirst"),
56 ExplorationType::ScoreFirst => write!(f, "ScoreFirst"),
57 ExplorationType::OptimisticBoundFirst => write!(f, "OptimisticBoundFirst"),
58 }
59 }
60}
61
62#[derive(Debug, Clone)]
64pub struct ExhaustiveSearchConfig {
65 pub exploration_type: ExplorationType,
67 pub node_limit: Option<u64>,
69 pub depth_limit: Option<usize>,
71 pub enable_pruning: bool,
73}
74
75impl Default for ExhaustiveSearchConfig {
76 fn default() -> Self {
77 Self {
78 exploration_type: ExplorationType::DepthFirst,
79 node_limit: Some(10_000),
80 depth_limit: None,
81 enable_pruning: true,
82 }
83 }
84}
85
86struct PriorityNode<S: PlanningSolution> {
88 index: usize,
89 node: ExhaustiveSearchNode<S>,
90 exploration_type: ExplorationType,
91}
92
93impl<S: PlanningSolution> PriorityNode<S> {
94 fn new(index: usize, node: ExhaustiveSearchNode<S>, exploration_type: ExplorationType) -> Self {
95 Self {
96 index,
97 node,
98 exploration_type,
99 }
100 }
101}
102
103impl<S: PlanningSolution> Eq for PriorityNode<S> {}
104
105impl<S: PlanningSolution> PartialEq for PriorityNode<S> {
106 fn eq(&self, other: &Self) -> bool {
107 self.index == other.index
108 }
109}
110
111impl<S: PlanningSolution> Ord for PriorityNode<S> {
112 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
113 match self.exploration_type {
114 ExplorationType::DepthFirst => {
115 self.node.depth().cmp(&other.node.depth())
117 }
118 ExplorationType::BreadthFirst => {
119 other.node.depth().cmp(&self.node.depth())
121 }
122 ExplorationType::ScoreFirst => {
123 self.node
125 .score()
126 .partial_cmp(other.node.score())
127 .unwrap_or(std::cmp::Ordering::Equal)
128 }
129 ExplorationType::OptimisticBoundFirst => {
130 match (self.node.optimistic_bound(), other.node.optimistic_bound()) {
132 (Some(a), Some(b)) => a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal),
133 (Some(_), None) => std::cmp::Ordering::Greater,
134 (None, Some(_)) => std::cmp::Ordering::Less,
135 (None, None) => std::cmp::Ordering::Equal,
136 }
137 }
138 }
139 }
140}
141
142impl<S: PlanningSolution> PartialOrd for PriorityNode<S> {
143 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
144 Some(self.cmp(other))
145 }
146}
147
148pub struct ExhaustiveSearchPhase<S: PlanningSolution> {
155 decider: Box<dyn ExhaustiveSearchDecider<S>>,
157 config: ExhaustiveSearchConfig,
159}
160
161impl<S: PlanningSolution> ExhaustiveSearchPhase<S> {
162 pub fn new(
164 decider: Box<dyn ExhaustiveSearchDecider<S>>,
165 config: ExhaustiveSearchConfig,
166 ) -> Self {
167 Self { decider, config }
168 }
169
170 pub fn depth_first(decider: Box<dyn ExhaustiveSearchDecider<S>>) -> Self {
172 Self::new(
173 decider,
174 ExhaustiveSearchConfig {
175 exploration_type: ExplorationType::DepthFirst,
176 ..Default::default()
177 },
178 )
179 }
180
181 pub fn breadth_first(decider: Box<dyn ExhaustiveSearchDecider<S>>) -> Self {
183 Self::new(
184 decider,
185 ExhaustiveSearchConfig {
186 exploration_type: ExplorationType::BreadthFirst,
187 ..Default::default()
188 },
189 )
190 }
191
192 pub fn score_first(decider: Box<dyn ExhaustiveSearchDecider<S>>) -> Self {
194 Self::new(
195 decider,
196 ExhaustiveSearchConfig {
197 exploration_type: ExplorationType::ScoreFirst,
198 ..Default::default()
199 },
200 )
201 }
202}
203
204impl<S: PlanningSolution> Debug for ExhaustiveSearchPhase<S> {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 f.debug_struct("ExhaustiveSearchPhase")
207 .field("decider", &self.decider)
208 .field("config", &self.config)
209 .finish()
210 }
211}
212
213impl<S: PlanningSolution> Phase<S> for ExhaustiveSearchPhase<S> {
214 fn solve(&mut self, solver_scope: &mut SolverScope<S>) {
215 let mut phase_scope = PhaseScope::new(solver_scope, 0);
216
217 let total_entities = self.decider.total_entities(phase_scope.score_director());
219 if total_entities == 0 {
220 return;
221 }
222
223 let initial_score = phase_scope.calculate_score();
225
226 let root = ExhaustiveSearchNode::root(initial_score.clone());
228
229 let mut best_score: Option<S::Score> = None;
231 let mut nodes_explored: u64 = 0;
232
233 let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
235 frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
236
237 let mut all_nodes: Vec<ExhaustiveSearchNode<S>> = Vec::new();
239
240 while let Some(priority_node) = frontier.pop() {
241 let node = priority_node.node;
242 let node_index = all_nodes.len();
243
244 if let Some(limit) = self.config.node_limit {
246 if nodes_explored >= limit {
247 break;
248 }
249 }
250
251 if let Some(limit) = self.config.depth_limit {
253 if node.depth() >= limit {
254 continue;
255 }
256 }
257
258 if self.config.enable_pruning {
260 if let Some(ref best) = best_score {
261 if node.can_prune(best) {
262 continue;
263 }
264 }
265 }
266
267 nodes_explored += 1;
268
269 if node.is_leaf(total_entities) {
271 let is_better = match &best_score {
272 None => true,
273 Some(best) => node.score() > best,
274 };
275
276 if is_better {
277 best_score = Some(node.score().clone());
278 phase_scope.update_best_solution();
279 }
280
281 all_nodes.push(node);
282 continue;
283 }
284
285 let children = self
287 .decider
288 .expand(node_index, &node, phase_scope.score_director_mut());
289
290 all_nodes.push(node);
292
293 for child in children {
295 if self.config.enable_pruning {
297 if let Some(ref best) = best_score {
298 if child.can_prune(best) {
299 continue;
300 }
301 }
302 }
303
304 let child_index = all_nodes.len() + frontier.len();
305 frontier.push(PriorityNode::new(
306 child_index,
307 child,
308 self.config.exploration_type,
309 ));
310 }
311 }
312 }
313
314 fn phase_type_name(&self) -> &'static str {
315 "ExhaustiveSearch"
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use solverforge_core::score::SimpleScore;
323
324 #[derive(Clone, Debug)]
325 struct TestSolution {
326 score: Option<SimpleScore>,
327 }
328
329 impl PlanningSolution for TestSolution {
330 type Score = SimpleScore;
331
332 fn score(&self) -> Option<Self::Score> {
333 self.score
334 }
335
336 fn set_score(&mut self, score: Option<Self::Score>) {
337 self.score = score;
338 }
339 }
340
341 fn set_row(_s: &mut TestSolution, _idx: usize, _v: Option<i32>) {}
343
344 #[test]
345 fn test_exploration_type_display() {
346 assert_eq!(format!("{}", ExplorationType::DepthFirst), "DepthFirst");
347 assert_eq!(format!("{}", ExplorationType::BreadthFirst), "BreadthFirst");
348 assert_eq!(format!("{}", ExplorationType::ScoreFirst), "ScoreFirst");
349 assert_eq!(
350 format!("{}", ExplorationType::OptimisticBoundFirst),
351 "OptimisticBoundFirst"
352 );
353 }
354
355 #[test]
356 fn test_exploration_type_default() {
357 assert_eq!(ExplorationType::default(), ExplorationType::DepthFirst);
358 }
359
360 #[test]
361 fn test_config_default() {
362 let config = ExhaustiveSearchConfig::default();
363 assert_eq!(config.exploration_type, ExplorationType::DepthFirst);
364 assert_eq!(config.node_limit, Some(10_000));
365 assert!(config.depth_limit.is_none());
366 assert!(config.enable_pruning);
367 }
368
369 #[test]
370 fn test_phase_type_name() {
371 let decider: SimpleDecider<TestSolution, i32> =
372 SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
373 let phase = ExhaustiveSearchPhase::depth_first(Box::new(decider));
374
375 assert_eq!(phase.phase_type_name(), "ExhaustiveSearch");
376 }
377
378 #[test]
379 fn test_phase_debug() {
380 let decider: SimpleDecider<TestSolution, i32> =
381 SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
382 let phase = ExhaustiveSearchPhase::depth_first(Box::new(decider));
383
384 let debug = format!("{:?}", phase);
385 assert!(debug.contains("ExhaustiveSearchPhase"));
386 assert!(debug.contains("DepthFirst"));
387 }
388}