solverforge_solver/phase/exhaustive/
node.rs1use std::marker::PhantomData;
6
7use crate::heuristic::Move;
8use solverforge_core::domain::PlanningSolution;
9
10#[derive(Debug, Clone)]
18pub struct ExhaustiveSearchNode<S: PlanningSolution> {
19 depth: usize,
21
22 score: S::Score,
24
25 optimistic_bound: Option<S::Score>,
28
29 entity_index: Option<usize>,
31
32 value_index: Option<usize>,
34
35 parent_index: Option<usize>,
37
38 expanded: bool,
40}
41
42impl<S: PlanningSolution> ExhaustiveSearchNode<S> {
43 pub fn root(score: S::Score) -> Self {
45 Self {
46 depth: 0,
47 score,
48 optimistic_bound: None,
49 entity_index: None,
50 value_index: None,
51 parent_index: None,
52 expanded: false,
53 }
54 }
55
56 pub fn child(
58 parent_index: usize,
59 depth: usize,
60 score: S::Score,
61 entity_index: usize,
62 value_index: usize,
63 ) -> Self {
64 Self {
65 depth,
66 score,
67 optimistic_bound: None,
68 entity_index: Some(entity_index),
69 value_index: Some(value_index),
70 parent_index: Some(parent_index),
71 expanded: false,
72 }
73 }
74
75 #[inline]
77 pub fn depth(&self) -> usize {
78 self.depth
79 }
80
81 #[inline]
83 pub fn score(&self) -> &S::Score {
84 &self.score
85 }
86
87 pub fn set_score(&mut self, score: S::Score) {
89 self.score = score;
90 }
91
92 #[inline]
94 pub fn optimistic_bound(&self) -> Option<&S::Score> {
95 self.optimistic_bound.as_ref()
96 }
97
98 pub fn set_optimistic_bound(&mut self, bound: S::Score) {
100 self.optimistic_bound = Some(bound);
101 }
102
103 #[inline]
105 pub fn entity_index(&self) -> Option<usize> {
106 self.entity_index
107 }
108
109 #[inline]
111 pub fn value_index(&self) -> Option<usize> {
112 self.value_index
113 }
114
115 #[inline]
117 pub fn parent_index(&self) -> Option<usize> {
118 self.parent_index
119 }
120
121 #[inline]
123 pub fn is_expanded(&self) -> bool {
124 self.expanded
125 }
126
127 pub fn mark_expanded(&mut self) {
129 self.expanded = true;
130 }
131
132 pub fn is_leaf(&self, total_entities: usize) -> bool {
134 self.depth >= total_entities
135 }
136
137 pub fn can_prune(&self, best_score: &S::Score) -> bool {
142 match &self.optimistic_bound {
143 Some(bound) => bound <= best_score,
144 None => false,
145 }
146 }
147}
148
149#[derive(Debug, Clone)]
155pub struct MoveSequence<S, M>
156where
157 S: PlanningSolution,
158 M: Move<S>,
159{
160 moves: Vec<M>,
162 _phantom: PhantomData<fn() -> S>,
163}
164
165impl<S, M> MoveSequence<S, M>
166where
167 S: PlanningSolution,
168 M: Move<S>,
169{
170 pub fn new() -> Self {
172 Self {
173 moves: Vec::new(),
174 _phantom: PhantomData,
175 }
176 }
177
178 pub fn with_capacity(capacity: usize) -> Self {
180 Self {
181 moves: Vec::with_capacity(capacity),
182 _phantom: PhantomData,
183 }
184 }
185
186 pub fn push(&mut self, m: M) {
188 self.moves.push(m);
189 }
190
191 pub fn pop(&mut self) -> Option<M> {
193 self.moves.pop()
194 }
195
196 pub fn len(&self) -> usize {
198 self.moves.len()
199 }
200
201 pub fn is_empty(&self) -> bool {
203 self.moves.is_empty()
204 }
205
206 pub fn iter(&self) -> impl Iterator<Item = &M> {
208 self.moves.iter()
209 }
210
211 pub fn clear(&mut self) {
213 self.moves.clear();
214 }
215}
216
217impl<S, M> Default for MoveSequence<S, M>
218where
219 S: PlanningSolution,
220 M: Move<S>,
221{
222 fn default() -> Self {
223 Self::new()
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::heuristic::r#move::ChangeMove;
231 use solverforge_core::score::SoftScore;
232
233 #[derive(Clone, Debug)]
234 struct TestSolution {
235 values: Vec<Option<i32>>,
236 score: Option<SoftScore>,
237 }
238
239 impl PlanningSolution for TestSolution {
240 type Score = SoftScore;
241
242 fn score(&self) -> Option<Self::Score> {
243 self.score
244 }
245
246 fn set_score(&mut self, score: Option<Self::Score>) {
247 self.score = score;
248 }
249 }
250
251 fn get_value(s: &TestSolution, idx: usize) -> Option<i32> {
253 s.values.get(idx).copied().flatten()
254 }
255
256 fn set_value(s: &mut TestSolution, idx: usize, v: Option<i32>) {
258 if let Some(val) = s.values.get_mut(idx) {
259 *val = v;
260 }
261 }
262
263 #[test]
264 fn test_root_node() {
265 let node: ExhaustiveSearchNode<TestSolution> = ExhaustiveSearchNode::root(SoftScore::of(0));
266
267 assert_eq!(node.depth(), 0);
268 assert_eq!(node.score(), &SoftScore::of(0));
269 assert!(node.parent_index().is_none());
270 assert!(node.entity_index().is_none());
271 assert!(!node.is_expanded());
272 }
273
274 #[test]
275 fn test_child_node() {
276 let node: ExhaustiveSearchNode<TestSolution> =
277 ExhaustiveSearchNode::child(0, 1, SoftScore::of(-1), 0, 2);
278
279 assert_eq!(node.depth(), 1);
280 assert_eq!(node.score(), &SoftScore::of(-1));
281 assert_eq!(node.parent_index(), Some(0));
282 assert_eq!(node.entity_index(), Some(0));
283 assert_eq!(node.value_index(), Some(2));
284 }
285
286 #[test]
287 fn test_is_leaf() {
288 let node: ExhaustiveSearchNode<TestSolution> =
289 ExhaustiveSearchNode::child(0, 4, SoftScore::of(0), 3, 1);
290
291 assert!(node.is_leaf(4));
292 assert!(!node.is_leaf(5));
293 }
294
295 #[test]
296 fn test_optimistic_bound_pruning() {
297 let mut node: ExhaustiveSearchNode<TestSolution> =
298 ExhaustiveSearchNode::root(SoftScore::of(-5));
299
300 assert!(!node.can_prune(&SoftScore::of(0)));
302
303 node.set_optimistic_bound(SoftScore::of(-2));
305 assert!(node.can_prune(&SoftScore::of(0)));
306
307 node.set_optimistic_bound(SoftScore::of(5));
309 assert!(!node.can_prune(&SoftScore::of(0)));
310 }
311
312 type TestMove = ChangeMove<TestSolution, i32>;
313
314 #[test]
315 fn test_move_sequence() {
316 let mut seq: MoveSequence<TestSolution, TestMove> = MoveSequence::new();
317
318 assert!(seq.is_empty());
319 assert_eq!(seq.len(), 0);
320
321 seq.push(ChangeMove::new(
322 0,
323 Some(42),
324 get_value,
325 set_value,
326 "test",
327 0,
328 ));
329 assert_eq!(seq.len(), 1);
330
331 let m = seq.pop();
332 assert!(m.is_some());
333 assert!(seq.is_empty());
334 }
335}