solverforge_solver/phase/exhaustive/
phase.rs1use 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
19pub struct ExhaustiveSearchPhase<Dec> {
56 decider: Dec,
58 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 let total_entities = self.decider.total_entities(phase_scope.score_director());
145 if total_entities == 0 {
146 return;
147 }
148
149 let initial_score = phase_scope.calculate_score();
151
152 let root = ExhaustiveSearchNode::root(initial_score);
154
155 let mut best_score: Option<S::Score> = None;
157 let mut nodes_explored: u64 = 0;
158 let mut bounded = false;
159
160 let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
162 frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
163
164 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 if let Some(limit) = self.config.node_limit {
177 if nodes_explored >= limit {
178 bounded = true;
179 break;
180 }
181 }
182
183 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 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 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 let children = self
225 .decider
226 .expand(node_index, &node, phase_scope.score_director_mut());
227
228 all_nodes.push(node);
230
231 for child in children {
233 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;