Skip to main content

solverforge_solver/phase/exhaustive/
phase.rs

1//! ExhaustiveSearchPhase implementation.
2
3use 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
19/// Exhaustive search phase that explores all possible solutions.
20///
21/// This phase systematically explores the solution space using a
22/// branch-and-bound algorithm. It maintains a tree of partial solutions
23/// and uses pruning to avoid exploring branches that cannot improve
24/// on the best solution found.
25///
26/// # Type Parameters
27/// * `Dec` - The decider type that generates child nodes
28///
29/// # Example
30///
31/// ```
32/// use solverforge_solver::phase::exhaustive::{ExhaustiveSearchPhase, SimpleDecider, ExhaustiveSearchConfig};
33/// use solverforge_core::domain::PlanningSolution;
34/// use solverforge_core::score::SoftScore;
35///
36/// #[derive(Clone, Debug)]
37/// struct MySolution {
38///     values: Vec<Option<i32>>,
39///     score: Option<SoftScore>,
40/// }
41///
42/// impl PlanningSolution for MySolution {
43///     type Score = SoftScore;
44///     fn score(&self) -> Option<Self::Score> { self.score }
45///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
46/// }
47///
48/// fn set_value(s: &mut MySolution, idx: usize, v: Option<i32>) {
49///     if let Some(slot) = s.values.get_mut(idx) { *slot = v; }
50/// }
51///
52/// let decider = SimpleDecider::<MySolution, i32>::new(0, "value", vec![1, 2, 3], set_value);
53/// let phase = ExhaustiveSearchPhase::depth_first(decider);
54/// ```
55pub struct ExhaustiveSearchPhase<Dec> {
56    /// The decider that generates child nodes.
57    decider: Dec,
58    /// Configuration for this phase.
59    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    /// Creates a new exhaustive search phase.
73    pub fn new(decider: Dec, config: ExhaustiveSearchConfig) -> Self {
74        Self { decider, config }
75    }
76
77    /// Creates a depth-first exhaustive search phase.
78    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    /// Creates a breadth-first exhaustive search phase.
89    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    /// Creates a score-first exhaustive search phase.
100    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    /// Returns the phase type name.
111    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        // Get total entities
127        let total_entities = self.decider.total_entities(phase_scope.score_director());
128        if total_entities == 0 {
129            return;
130        }
131
132        // Calculate initial score
133        let initial_score = phase_scope.calculate_score();
134
135        // Create root node
136        let root = ExhaustiveSearchNode::root(initial_score);
137
138        // Initialize best score
139        let mut best_score: Option<S::Score> = None;
140        let mut nodes_explored: u64 = 0;
141
142        // Priority queue for node exploration
143        let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
144        frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
145
146        // Node storage
147        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            // Check node limit
154            if let Some(limit) = self.config.node_limit {
155                if nodes_explored >= limit {
156                    break;
157                }
158            }
159
160            // Check depth limit
161            if let Some(limit) = self.config.depth_limit {
162                if node.depth() >= limit {
163                    continue;
164                }
165            }
166
167            // Check pruning
168            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            // Check if this is a complete solution (leaf node)
179            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            // Expand node to generate children
195            let children = self
196                .decider
197                .expand(node_index, &node, phase_scope.score_director_mut());
198
199            // Store current node
200            all_nodes.push(node);
201
202            // Add children to frontier
203            for child in children {
204                // Skip if prunable
205                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    // Dummy setter for tests
255    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}