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