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    pub fn new(decider: Dec, config: ExhaustiveSearchConfig) -> Self {
73        Self { decider, config }
74    }
75
76    pub fn depth_first(decider: Dec) -> Self {
77        Self::new(
78            decider,
79            ExhaustiveSearchConfig {
80                exploration_type: ExplorationType::DepthFirst,
81                ..Default::default()
82            },
83        )
84    }
85
86    pub fn breadth_first(decider: Dec) -> Self {
87        Self::new(
88            decider,
89            ExhaustiveSearchConfig {
90                exploration_type: ExplorationType::BreadthFirst,
91                ..Default::default()
92            },
93        )
94    }
95
96    pub fn score_first(decider: Dec) -> Self {
97        Self::new(
98            decider,
99            ExhaustiveSearchConfig {
100                exploration_type: ExplorationType::ScoreFirst,
101                ..Default::default()
102            },
103        )
104    }
105
106    pub fn phase_type_name(&self) -> &'static str {
107        "ExhaustiveSearch"
108    }
109}
110
111impl<S, D, BestCb, Dec> Phase<S, D, BestCb> for ExhaustiveSearchPhase<Dec>
112where
113    S: PlanningSolution,
114    D: Director<S>,
115    BestCb: BestSolutionCallback<S>,
116    Dec: ExhaustiveSearchDecider<S, D>,
117{
118    fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
119        let mut phase_scope = PhaseScope::new(solver_scope, 0);
120
121        // Get total entities
122        let total_entities = self.decider.total_entities(phase_scope.score_director());
123        if total_entities == 0 {
124            return;
125        }
126
127        // Calculate initial score
128        let initial_score = phase_scope.calculate_score();
129
130        // Create root node
131        let root = ExhaustiveSearchNode::root(initial_score);
132
133        // Initialize best score
134        let mut best_score: Option<S::Score> = None;
135        let mut nodes_explored: u64 = 0;
136
137        // Priority queue for node exploration
138        let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
139        frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
140
141        // Node storage
142        let mut all_nodes: Vec<ExhaustiveSearchNode<S>> = Vec::new();
143
144        while let Some(priority_node) = frontier.pop() {
145            let node = priority_node.node;
146            let node_index = all_nodes.len();
147
148            // Check node limit
149            if let Some(limit) = self.config.node_limit {
150                if nodes_explored >= limit {
151                    break;
152                }
153            }
154
155            // Check depth limit
156            if let Some(limit) = self.config.depth_limit {
157                if node.depth() >= limit {
158                    continue;
159                }
160            }
161
162            // Check pruning
163            if self.config.enable_pruning {
164                if let Some(ref best) = best_score {
165                    if node.can_prune(best) {
166                        continue;
167                    }
168                }
169            }
170
171            nodes_explored += 1;
172
173            // Check if this is a complete solution (leaf node)
174            if node.is_leaf(total_entities) {
175                let is_better = match &best_score {
176                    None => true,
177                    Some(best) => node.score() > best,
178                };
179
180                if is_better {
181                    best_score = Some(*node.score());
182                    phase_scope.update_best_solution();
183                }
184
185                all_nodes.push(node);
186                continue;
187            }
188
189            // Expand node to generate children
190            let children = self
191                .decider
192                .expand(node_index, &node, phase_scope.score_director_mut());
193
194            // Store current node
195            all_nodes.push(node);
196
197            // Add children to frontier
198            for child in children {
199                // Skip if prunable
200                if self.config.enable_pruning {
201                    if let Some(ref best) = best_score {
202                        if child.can_prune(best) {
203                            continue;
204                        }
205                    }
206                }
207
208                let child_index = all_nodes.len() + frontier.len();
209                frontier.push(PriorityNode::new(
210                    child_index,
211                    child,
212                    self.config.exploration_type,
213                ));
214            }
215        }
216    }
217
218    fn phase_type_name(&self) -> &'static str {
219        "ExhaustiveSearch"
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use solverforge_core::domain::PlanningSolution;
226    use solverforge_core::score::SoftScore;
227
228    use super::*;
229    use crate::phase::exhaustive::decider::SimpleDecider;
230    use crate::phase::exhaustive::exploration_type::ExplorationType;
231
232    #[derive(Clone, Debug)]
233    struct TestSolution {
234        score: Option<SoftScore>,
235    }
236
237    impl PlanningSolution for TestSolution {
238        type Score = SoftScore;
239
240        fn score(&self) -> Option<Self::Score> {
241            self.score
242        }
243
244        fn set_score(&mut self, score: Option<Self::Score>) {
245            self.score = score;
246        }
247    }
248
249    // Dummy setter for tests
250    fn set_row(_s: &mut TestSolution, _idx: usize, _v: Option<i32>) {}
251
252    #[test]
253    fn test_exploration_type_display() {
254        assert_eq!(format!("{}", ExplorationType::DepthFirst), "DepthFirst");
255        assert_eq!(format!("{}", ExplorationType::BreadthFirst), "BreadthFirst");
256        assert_eq!(format!("{}", ExplorationType::ScoreFirst), "ScoreFirst");
257        assert_eq!(
258            format!("{}", ExplorationType::OptimisticBoundFirst),
259            "OptimisticBoundFirst"
260        );
261    }
262
263    #[test]
264    fn test_exploration_type_default() {
265        assert_eq!(ExplorationType::default(), ExplorationType::DepthFirst);
266    }
267
268    #[test]
269    fn test_config_default() {
270        let config = ExhaustiveSearchConfig::default();
271        assert_eq!(config.exploration_type, ExplorationType::DepthFirst);
272        assert_eq!(config.node_limit, Some(10_000));
273        assert!(config.depth_limit.is_none());
274        assert!(config.enable_pruning);
275    }
276
277    #[test]
278    fn test_phase_type_name() {
279        let decider: SimpleDecider<TestSolution, i32> =
280            SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
281        let phase = ExhaustiveSearchPhase::depth_first(decider);
282
283        assert_eq!(phase.phase_type_name(), "ExhaustiveSearch");
284    }
285
286    #[test]
287    fn test_phase_debug() {
288        let decider: SimpleDecider<TestSolution, i32> =
289            SimpleDecider::new(0, "row", vec![0, 1, 2, 3], set_row);
290        let phase = ExhaustiveSearchPhase::depth_first(decider);
291
292        let debug = format!("{:?}", phase);
293        assert!(debug.contains("ExhaustiveSearchPhase"));
294        assert!(debug.contains("DepthFirst"));
295    }
296}