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;
22use solverforge_scoring::ScoreDirector;
23
24use crate::phase::Phase;
25use crate::scope::BestSolutionCallback;
26use crate::scope::{PhaseScope, SolverScope};
27
28pub use bounder::{BounderType, FixedOffsetBounder, ScoreBounder, SimpleScoreBounder};
29pub use decider::{ExhaustiveSearchDecider, SimpleDecider};
30pub use node::{ExhaustiveSearchNode, MoveSequence};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
34pub enum ExplorationType {
35 #[default]
38 DepthFirst,
39
40 BreadthFirst,
43
44 ScoreFirst,
47
48 OptimisticBoundFirst,
51}
52
53impl std::fmt::Display for ExplorationType {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 ExplorationType::DepthFirst => write!(f, "DepthFirst"),
57 ExplorationType::BreadthFirst => write!(f, "BreadthFirst"),
58 ExplorationType::ScoreFirst => write!(f, "ScoreFirst"),
59 ExplorationType::OptimisticBoundFirst => write!(f, "OptimisticBoundFirst"),
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct ExhaustiveSearchConfig {
67 pub exploration_type: ExplorationType,
69 pub node_limit: Option<u64>,
71 pub depth_limit: Option<usize>,
73 pub enable_pruning: bool,
75}
76
77impl Default for ExhaustiveSearchConfig {
78 fn default() -> Self {
79 Self {
80 exploration_type: ExplorationType::DepthFirst,
81 node_limit: Some(10_000),
82 depth_limit: None,
83 enable_pruning: true,
84 }
85 }
86}
87
88struct PriorityNode<S: PlanningSolution> {
90 index: usize,
91 node: ExhaustiveSearchNode<S>,
92 exploration_type: ExplorationType,
93}
94
95impl<S: PlanningSolution> PriorityNode<S> {
96 fn new(index: usize, node: ExhaustiveSearchNode<S>, exploration_type: ExplorationType) -> Self {
97 Self {
98 index,
99 node,
100 exploration_type,
101 }
102 }
103}
104
105impl<S: PlanningSolution> Eq for PriorityNode<S> {}
106
107impl<S: PlanningSolution> PartialEq for PriorityNode<S> {
108 fn eq(&self, other: &Self) -> bool {
109 self.index == other.index
110 }
111}
112
113impl<S: PlanningSolution> Ord for PriorityNode<S> {
114 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
115 match self.exploration_type {
116 ExplorationType::DepthFirst => {
117 self.node.depth().cmp(&other.node.depth())
119 }
120 ExplorationType::BreadthFirst => {
121 other.node.depth().cmp(&self.node.depth())
123 }
124 ExplorationType::ScoreFirst => {
125 self.node
127 .score()
128 .partial_cmp(other.node.score())
129 .unwrap_or(std::cmp::Ordering::Equal)
130 }
131 ExplorationType::OptimisticBoundFirst => {
132 match (self.node.optimistic_bound(), other.node.optimistic_bound()) {
134 (Some(a), Some(b)) => a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal),
135 (Some(_), None) => std::cmp::Ordering::Greater,
136 (None, Some(_)) => std::cmp::Ordering::Less,
137 (None, None) => std::cmp::Ordering::Equal,
138 }
139 }
140 }
141 }
142}
143
144impl<S: PlanningSolution> PartialOrd for PriorityNode<S> {
145 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
146 Some(self.cmp(other))
147 }
148}
149
150pub struct ExhaustiveSearchPhase<Dec> {
187 decider: Dec,
189 config: ExhaustiveSearchConfig,
191}
192
193impl<Dec: Debug> Debug for ExhaustiveSearchPhase<Dec> {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 f.debug_struct("ExhaustiveSearchPhase")
196 .field("decider", &self.decider)
197 .field("config", &self.config)
198 .finish()
199 }
200}
201
202impl<Dec> ExhaustiveSearchPhase<Dec> {
203 pub fn new(decider: Dec, config: ExhaustiveSearchConfig) -> Self {
205 Self { decider, config }
206 }
207
208 pub fn depth_first(decider: Dec) -> Self {
210 Self::new(
211 decider,
212 ExhaustiveSearchConfig {
213 exploration_type: ExplorationType::DepthFirst,
214 ..Default::default()
215 },
216 )
217 }
218
219 pub fn breadth_first(decider: Dec) -> Self {
221 Self::new(
222 decider,
223 ExhaustiveSearchConfig {
224 exploration_type: ExplorationType::BreadthFirst,
225 ..Default::default()
226 },
227 )
228 }
229
230 pub fn score_first(decider: Dec) -> Self {
232 Self::new(
233 decider,
234 ExhaustiveSearchConfig {
235 exploration_type: ExplorationType::ScoreFirst,
236 ..Default::default()
237 },
238 )
239 }
240
241 pub fn phase_type_name(&self) -> &'static str {
243 "ExhaustiveSearch"
244 }
245}
246
247impl<S, D, BestCb, Dec> Phase<S, D, BestCb> for ExhaustiveSearchPhase<Dec>
248where
249 S: PlanningSolution,
250 D: ScoreDirector<S>,
251 BestCb: BestSolutionCallback<S>,
252 Dec: ExhaustiveSearchDecider<S, D>,
253{
254 fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
255 let mut phase_scope = PhaseScope::new(solver_scope, 0);
256
257 let total_entities = self.decider.total_entities(phase_scope.score_director());
259 if total_entities == 0 {
260 return;
261 }
262
263 let initial_score = phase_scope.calculate_score();
265
266 let root = ExhaustiveSearchNode::root(initial_score);
268
269 let mut best_score: Option<S::Score> = None;
271 let mut nodes_explored: u64 = 0;
272
273 let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
275 frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
276
277 let mut all_nodes: Vec<ExhaustiveSearchNode<S>> = Vec::new();
279
280 while let Some(priority_node) = frontier.pop() {
281 let node = priority_node.node;
282 let node_index = all_nodes.len();
283
284 if let Some(limit) = self.config.node_limit {
286 if nodes_explored >= limit {
287 break;
288 }
289 }
290
291 if let Some(limit) = self.config.depth_limit {
293 if node.depth() >= limit {
294 continue;
295 }
296 }
297
298 if self.config.enable_pruning {
300 if let Some(ref best) = best_score {
301 if node.can_prune(best) {
302 continue;
303 }
304 }
305 }
306
307 nodes_explored += 1;
308
309 if node.is_leaf(total_entities) {
311 let is_better = match &best_score {
312 None => true,
313 Some(best) => node.score() > best,
314 };
315
316 if is_better {
317 best_score = Some(*node.score());
318 phase_scope.update_best_solution();
319 }
320
321 all_nodes.push(node);
322 continue;
323 }
324
325 let children = self
327 .decider
328 .expand(node_index, &node, phase_scope.score_director_mut());
329
330 all_nodes.push(node);
332
333 for child in children {
335 if self.config.enable_pruning {
337 if let Some(ref best) = best_score {
338 if child.can_prune(best) {
339 continue;
340 }
341 }
342 }
343
344 let child_index = all_nodes.len() + frontier.len();
345 frontier.push(PriorityNode::new(
346 child_index,
347 child,
348 self.config.exploration_type,
349 ));
350 }
351 }
352 }
353
354 fn phase_type_name(&self) -> &'static str {
355 "ExhaustiveSearch"
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use solverforge_core::score::SimpleScore;
363
364 #[derive(Clone, Debug)]
365 struct TestSolution {
366 score: Option<SimpleScore>,
367 }
368
369 impl PlanningSolution for TestSolution {
370 type Score = SimpleScore;
371
372 fn score(&self) -> Option<Self::Score> {
373 self.score
374 }
375
376 fn set_score(&mut self, score: Option<Self::Score>) {
377 self.score = score;
378 }
379 }
380
381 fn set_row(_s: &mut TestSolution, _idx: usize, _v: Option<i32>) {}
383
384 #[test]
385 fn test_exploration_type_display() {
386 assert_eq!(format!("{}", ExplorationType::DepthFirst), "DepthFirst");
387 assert_eq!(format!("{}", ExplorationType::BreadthFirst), "BreadthFirst");
388 assert_eq!(format!("{}", ExplorationType::ScoreFirst), "ScoreFirst");
389 assert_eq!(
390 format!("{}", ExplorationType::OptimisticBoundFirst),
391 "OptimisticBoundFirst"
392 );
393 }
394
395 #[test]
396 fn test_exploration_type_default() {
397 assert_eq!(ExplorationType::default(), ExplorationType::DepthFirst);
398 }
399
400 #[test]
401 fn test_config_default() {
402 let config = ExhaustiveSearchConfig::default();
403 assert_eq!(config.exploration_type, ExplorationType::DepthFirst);
404 assert_eq!(config.node_limit, Some(10_000));
405 assert!(config.depth_limit.is_none());
406 assert!(config.enable_pruning);
407 }
408
409 #[test]
410 fn test_phase_type_name() {
411 let decider: SimpleDecider<TestSolution, i32> =
412 SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
413 let phase = ExhaustiveSearchPhase::depth_first(decider);
414
415 assert_eq!(phase.phase_type_name(), "ExhaustiveSearch");
416 }
417
418 #[test]
419 fn test_phase_debug() {
420 let decider: SimpleDecider<TestSolution, i32> =
421 SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
422 let phase = ExhaustiveSearchPhase::depth_first(decider);
423
424 let debug = format!("{:?}", phase);
425 assert!(debug.contains("ExhaustiveSearchPhase"));
426 assert!(debug.contains("DepthFirst"));
427 }
428}