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::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
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
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 let total_entities = self.decider.total_entities(phase_scope.score_director());
123 if total_entities == 0 {
124 return;
125 }
126
127 let initial_score = phase_scope.calculate_score();
129
130 let root = ExhaustiveSearchNode::root(initial_score);
132
133 let mut best_score: Option<S::Score> = None;
135 let mut nodes_explored: u64 = 0;
136
137 let mut frontier: BinaryHeap<PriorityNode<S>> = BinaryHeap::new();
139 frontier.push(PriorityNode::new(0, root, self.config.exploration_type));
140
141 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 if let Some(limit) = self.config.node_limit {
150 if nodes_explored >= limit {
151 break;
152 }
153 }
154
155 if let Some(limit) = self.config.depth_limit {
157 if node.depth() >= limit {
158 continue;
159 }
160 }
161
162 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 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 let children = self
191 .decider
192 .expand(node_index, &node, phase_scope.score_director_mut());
193
194 all_nodes.push(node);
196
197 for child in children {
199 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 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}