solverforge_solver/phase/exhaustive/
mod.rs

1//! Exhaustive search phase using branch-and-bound.
2//!
3//! Exhaustive search explores the entire solution space systematically,
4//! using pruning to avoid exploring branches that cannot improve on the
5//! best solution found so far.
6//!
7//! # Exploration Types
8//!
9//! - **Depth First**: Explores deepest nodes first (memory efficient)
10//! - **Breadth First**: Explores level by level (finds shortest paths)
11//! - **Score First**: Explores best-scoring nodes first (greedy)
12//! - **Optimistic Bound First**: Explores most promising bounds first (A*)
13
14mod 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/// Type of exploration strategy for exhaustive search.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub enum ExplorationType {
34    /// Depth-first search: explores deepest nodes first.
35    /// Most memory efficient, but may take longer to find optimal.
36    #[default]
37    DepthFirst,
38
39    /// Breadth-first search: explores level by level.
40    /// Memory intensive, but finds shortest solution paths.
41    BreadthFirst,
42
43    /// Score-first search: explores best-scoring nodes first.
44    /// Greedy approach that may find good solutions quickly.
45    ScoreFirst,
46
47    /// Optimistic bound first: explores most promising nodes first.
48    /// A*-like behavior, requires a good bounder.
49    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/// Configuration for exhaustive search phase.
64#[derive(Debug, Clone)]
65pub struct ExhaustiveSearchConfig {
66    /// The exploration type to use.
67    pub exploration_type: ExplorationType,
68    /// Maximum number of nodes to explore (None = unlimited).
69    pub node_limit: Option<u64>,
70    /// Maximum depth to explore (None = unlimited).
71    pub depth_limit: Option<usize>,
72    /// Whether to enable pruning based on bounds.
73    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
87/// A node wrapper for priority queue ordering.
88struct 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                // Higher depth = higher priority
117                self.node.depth().cmp(&other.node.depth())
118            }
119            ExplorationType::BreadthFirst => {
120                // Lower depth = higher priority (reversed)
121                other.node.depth().cmp(&self.node.depth())
122            }
123            ExplorationType::ScoreFirst => {
124                // Better score = higher priority
125                self.node
126                    .score()
127                    .partial_cmp(other.node.score())
128                    .unwrap_or(std::cmp::Ordering::Equal)
129            }
130            ExplorationType::OptimisticBoundFirst => {
131                // Better bound = higher priority
132                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
149/// Exhaustive search phase that explores all possible solutions.
150///
151/// This phase systematically explores the solution space using a
152/// branch-and-bound algorithm. It maintains a tree of partial solutions
153/// and uses pruning to avoid exploring branches that cannot improve
154/// on the best solution found.
155///
156/// # Type Parameters
157/// * `Dec` - The decider type that generates child nodes
158///
159/// # Example
160///
161/// ```
162/// use solverforge_solver::phase::exhaustive::{ExhaustiveSearchPhase, SimpleDecider, ExhaustiveSearchConfig};
163/// use solverforge_core::domain::PlanningSolution;
164/// use solverforge_core::score::SimpleScore;
165///
166/// #[derive(Clone, Debug)]
167/// struct MySolution {
168///     values: Vec<Option<i32>>,
169///     score: Option<SimpleScore>,
170/// }
171///
172/// impl PlanningSolution for MySolution {
173///     type Score = SimpleScore;
174///     fn score(&self) -> Option<Self::Score> { self.score }
175///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
176/// }
177///
178/// fn set_value(s: &mut MySolution, idx: usize, v: Option<i32>) {
179///     if let Some(slot) = s.values.get_mut(idx) { *slot = v; }
180/// }
181///
182/// let decider = SimpleDecider::<MySolution, i32>::new(0, "value", vec![1, 2, 3], set_value);
183/// let phase = ExhaustiveSearchPhase::depth_first(decider);
184/// ```
185pub struct ExhaustiveSearchPhase<Dec> {
186    /// The decider that generates child nodes.
187    decider: Dec,
188    /// Configuration for this phase.
189    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    /// Creates a new exhaustive search phase.
203    pub fn new(decider: Dec, config: ExhaustiveSearchConfig) -> Self {
204        Self { decider, config }
205    }
206
207    /// Creates a depth-first exhaustive search phase.
208    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    /// Creates a breadth-first exhaustive search phase.
219    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    /// Creates a score-first exhaustive search phase.
230    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    /// Returns the phase type name.
241    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        // Get total entities
256        let total_entities = self.decider.total_entities(phase_scope.score_director());
257        if total_entities == 0 {
258            return;
259        }
260
261        // Calculate initial score
262        let initial_score = phase_scope.calculate_score();
263
264        // Create root node
265        let root = ExhaustiveSearchNode::root(initial_score);
266
267        // Initialize best score
268        let mut best_score: Option<S::Score> = None;
269        let mut nodes_explored: u64 = 0;
270
271        // Priority queue for node exploration
272        let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
273        frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
274
275        // Node storage
276        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            // Check node limit
283            if let Some(limit) = self.config.node_limit {
284                if nodes_explored >= limit {
285                    break;
286                }
287            }
288
289            // Check depth limit
290            if let Some(limit) = self.config.depth_limit {
291                if node.depth() >= limit {
292                    continue;
293                }
294            }
295
296            // Check pruning
297            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            // Check if this is a complete solution (leaf node)
308            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            // Expand node to generate children
324            let children = self
325                .decider
326                .expand(node_index, &node, phase_scope.score_director_mut());
327
328            // Store current node
329            all_nodes.push(node);
330
331            // Add children to frontier
332            for child in children {
333                // Skip if prunable
334                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    // Dummy setter for tests
380    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}