solverforge_solver/phase/exhaustive/
node.rs1use std::marker::PhantomData;
7
8use crate::heuristic::Move;
9use solverforge_core::domain::PlanningSolution;
10
11#[derive(Debug, Clone)]
20pub struct ExhaustiveSearchNode<S: PlanningSolution> {
21 depth: usize,
23
24 score: S::Score,
26
27 optimistic_bound: Option<S::Score>,
30
31 entity_index: Option<usize>,
33
34 value_index: Option<usize>,
36
37 parent_index: Option<usize>,
39
40 expanded: bool,
42}
43
44impl<S: PlanningSolution> ExhaustiveSearchNode<S> {
45 pub fn root(score: S::Score) -> Self {
46 Self {
47 depth: 0,
48 score,
49 optimistic_bound: None,
50 entity_index: None,
51 value_index: None,
52 parent_index: None,
53 expanded: false,
54 }
55 }
56
57 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]
76 pub fn depth(&self) -> usize {
77 self.depth
78 }
79
80 #[inline]
81 pub fn score(&self) -> &S::Score {
82 &self.score
83 }
84
85 pub fn set_score(&mut self, score: S::Score) {
86 self.score = score;
87 }
88
89 #[inline]
90 pub fn optimistic_bound(&self) -> Option<&S::Score> {
91 self.optimistic_bound.as_ref()
92 }
93
94 pub fn set_optimistic_bound(&mut self, bound: S::Score) {
95 self.optimistic_bound = Some(bound);
96 }
97
98 #[inline]
99 pub fn entity_index(&self) -> Option<usize> {
100 self.entity_index
101 }
102
103 #[inline]
104 pub fn value_index(&self) -> Option<usize> {
105 self.value_index
106 }
107
108 #[inline]
109 pub fn parent_index(&self) -> Option<usize> {
110 self.parent_index
111 }
112
113 #[inline]
115 pub fn is_expanded(&self) -> bool {
116 self.expanded
117 }
118
119 pub fn mark_expanded(&mut self) {
121 self.expanded = true;
122 }
123
124 pub fn is_leaf(&self, total_entities: usize) -> bool {
125 self.depth >= total_entities
126 }
127
128 pub fn can_prune(&self, best_score: &S::Score) -> bool {
129 match &self.optimistic_bound {
130 Some(bound) => bound <= best_score,
131 None => false,
132 }
133 }
134}
135
136#[derive(Debug, Clone)]
143pub struct MoveSequence<S, M>
144where
145 S: PlanningSolution,
146 M: Move<S>,
147{
148 moves: Vec<M>,
150 _phantom: PhantomData<fn() -> S>,
151}
152
153impl<S, M> MoveSequence<S, M>
154where
155 S: PlanningSolution,
156 M: Move<S>,
157{
158 pub fn new() -> Self {
159 Self {
160 moves: Vec::new(),
161 _phantom: PhantomData,
162 }
163 }
164
165 pub fn with_capacity(capacity: usize) -> Self {
166 Self {
167 moves: Vec::with_capacity(capacity),
168 _phantom: PhantomData,
169 }
170 }
171
172 pub fn push(&mut self, m: M) {
173 self.moves.push(m);
174 }
175
176 pub fn pop(&mut self) -> Option<M> {
178 self.moves.pop()
179 }
180
181 pub fn len(&self) -> usize {
182 self.moves.len()
183 }
184
185 pub fn is_empty(&self) -> bool {
186 self.moves.is_empty()
187 }
188
189 pub fn iter(&self) -> impl Iterator<Item = &M> {
191 self.moves.iter()
192 }
193
194 pub fn clear(&mut self) {
196 self.moves.clear();
197 }
198}
199
200impl<S, M> Default for MoveSequence<S, M>
201where
202 S: PlanningSolution,
203 M: Move<S>,
204{
205 fn default() -> Self {
206 Self::new()
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use crate::heuristic::r#move::ChangeMove;
214 use solverforge_core::score::SoftScore;
215
216 #[derive(Clone, Debug)]
217 struct TestSolution {
218 values: Vec<Option<i32>>,
219 score: Option<SoftScore>,
220 }
221
222 impl PlanningSolution for TestSolution {
223 type Score = SoftScore;
224
225 fn score(&self) -> Option<Self::Score> {
226 self.score
227 }
228
229 fn set_score(&mut self, score: Option<Self::Score>) {
230 self.score = score;
231 }
232 }
233
234 fn get_value(s: &TestSolution, idx: usize) -> Option<i32> {
236 s.values.get(idx).copied().flatten()
237 }
238
239 fn set_value(s: &mut TestSolution, idx: usize, v: Option<i32>) {
241 if let Some(val) = s.values.get_mut(idx) {
242 *val = v;
243 }
244 }
245
246 #[test]
247 fn test_root_node() {
248 let node: ExhaustiveSearchNode<TestSolution> = ExhaustiveSearchNode::root(SoftScore::of(0));
249
250 assert_eq!(node.depth(), 0);
251 assert_eq!(node.score(), &SoftScore::of(0));
252 assert!(node.parent_index().is_none());
253 assert!(node.entity_index().is_none());
254 assert!(!node.is_expanded());
255 }
256
257 #[test]
258 fn test_child_node() {
259 let node: ExhaustiveSearchNode<TestSolution> =
260 ExhaustiveSearchNode::child(0, 1, SoftScore::of(-1), 0, 2);
261
262 assert_eq!(node.depth(), 1);
263 assert_eq!(node.score(), &SoftScore::of(-1));
264 assert_eq!(node.parent_index(), Some(0));
265 assert_eq!(node.entity_index(), Some(0));
266 assert_eq!(node.value_index(), Some(2));
267 }
268
269 #[test]
270 fn test_is_leaf() {
271 let node: ExhaustiveSearchNode<TestSolution> =
272 ExhaustiveSearchNode::child(0, 4, SoftScore::of(0), 3, 1);
273
274 assert!(node.is_leaf(4));
275 assert!(!node.is_leaf(5));
276 }
277
278 #[test]
279 fn test_optimistic_bound_pruning() {
280 let mut node: ExhaustiveSearchNode<TestSolution> =
281 ExhaustiveSearchNode::root(SoftScore::of(-5));
282
283 assert!(!node.can_prune(&SoftScore::of(0)));
285
286 node.set_optimistic_bound(SoftScore::of(-2));
288 assert!(node.can_prune(&SoftScore::of(0)));
289
290 node.set_optimistic_bound(SoftScore::of(5));
292 assert!(!node.can_prune(&SoftScore::of(0)));
293 }
294
295 type TestMove = ChangeMove<TestSolution, i32>;
296
297 #[test]
298 fn test_move_sequence() {
299 let mut seq: MoveSequence<TestSolution, TestMove> = MoveSequence::new();
300
301 assert!(seq.is_empty());
302 assert_eq!(seq.len(), 0);
303
304 seq.push(ChangeMove::new(
305 0,
306 Some(42),
307 get_value,
308 set_value,
309 "test",
310 0,
311 ));
312 assert_eq!(seq.len(), 1);
313
314 let m = seq.pop();
315 assert!(m.is_some());
316 assert!(seq.is_empty());
317 }
318}