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::ProgressCallback;
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    fn apply_node_path<'t, 'a, S, D, BestCb>(
111        &self,
112        phase_scope: &mut PhaseScope<'t, 'a, S, D, BestCb>,
113        all_nodes: &[ExhaustiveSearchNode<S>],
114        node: &ExhaustiveSearchNode<S>,
115    ) where
116        S: PlanningSolution,
117        D: Director<S>,
118        BestCb: ProgressCallback<S>,
119        Dec: ExhaustiveSearchDecider<S, D>,
120    {
121        let score_director = phase_scope.score_director_mut();
122        self.decider.reset_assignments(score_director);
123        for assignment in node.assignment_path(all_nodes) {
124            self.decider.apply_assignment(assignment, score_director);
125        }
126    }
127}
128
129impl<S, D, BestCb, Dec> Phase<S, D, BestCb> for ExhaustiveSearchPhase<Dec>
130where
131    S: PlanningSolution,
132    D: Director<S>,
133    BestCb: ProgressCallback<S>,
134    Dec: ExhaustiveSearchDecider<S, D>,
135{
136    fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
137        let mut phase_scope = PhaseScope::with_phase_type(solver_scope, 0, "ExhaustiveSearch");
138
139        if phase_scope.solver_scope_mut().should_terminate() {
140            return;
141        }
142
143        // Get total entities
144        let total_entities = self.decider.total_entities(phase_scope.score_director());
145        if total_entities == 0 {
146            return;
147        }
148
149        // Calculate initial score
150        let initial_score = phase_scope.calculate_score();
151
152        // Create root node
153        let root = ExhaustiveSearchNode::root(initial_score);
154
155        // Initialize best score
156        let mut best_score: Option<S::Score> = None;
157        let mut nodes_explored: u64 = 0;
158        let mut bounded = false;
159
160        // Priority queue for node exploration
161        let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
162        frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
163
164        // Node storage
165        let mut all_nodes: Vec<ExhaustiveSearchNode<S>> = Vec::new();
166
167        while let Some(priority_node) = frontier.pop() {
168            if phase_scope.solver_scope_mut().should_terminate() {
169                return;
170            }
171
172            let node = priority_node.node;
173            let node_index = all_nodes.len();
174
175            // Check node limit
176            if let Some(limit) = self.config.node_limit {
177                if nodes_explored >= limit {
178                    bounded = true;
179                    break;
180                }
181            }
182
183            // Check depth limit
184            if let Some(limit) = self.config.depth_limit {
185                if node.depth() >= limit && !node.is_leaf(total_entities) {
186                    bounded = true;
187                    continue;
188                }
189            }
190
191            // Check pruning
192            if self.config.enable_pruning {
193                if let Some(ref best) = best_score {
194                    if node.can_prune(best) {
195                        continue;
196                    }
197                }
198            }
199
200            nodes_explored += 1;
201            phase_scope.increment_step_count();
202            self.apply_node_path(&mut phase_scope, &all_nodes, &node);
203            let current_score = phase_scope.calculate_score();
204            let mut node = node;
205            node.set_score(current_score);
206
207            // Check if this is a complete solution (leaf node)
208            if node.is_leaf(total_entities) {
209                let is_better = match &best_score {
210                    None => true,
211                    Some(best) => current_score > *best,
212                };
213
214                if is_better {
215                    best_score = Some(current_score);
216                    phase_scope.update_best_solution();
217                }
218
219                all_nodes.push(node);
220                continue;
221            }
222
223            // Expand node to generate children
224            let children = self
225                .decider
226                .expand(node_index, &node, phase_scope.score_director_mut());
227
228            // Store current node
229            all_nodes.push(node);
230
231            // Add children to frontier
232            for child in children {
233                // Skip if prunable
234                if self.config.enable_pruning {
235                    if let Some(ref best) = best_score {
236                        if child.can_prune(best) {
237                            continue;
238                        }
239                    }
240                }
241
242                let child_index = all_nodes.len() + frontier.len();
243                frontier.push(PriorityNode::new(
244                    child_index,
245                    child,
246                    self.config.exploration_type,
247                ));
248            }
249        }
250
251        if bounded {
252            phase_scope.solver_scope_mut().mark_terminated_by_config();
253        }
254    }
255
256    fn phase_type_name(&self) -> &'static str {
257        "ExhaustiveSearch"
258    }
259}
260
261#[cfg(test)]
262#[path = "phase_tests.rs"]
263mod tests;