1use std::fmt::Debug;
7use std::marker::PhantomData;
8
9use solverforge_core::domain::PlanningSolution;
10use solverforge_core::score::Score;
11use solverforge_scoring::{RecordingScoreDirector, ScoreDirector};
12
13use crate::heuristic::r#move::Move;
14
15use super::Placement;
16
17pub trait ConstructionForager<S, M>: Send + Debug
25where
26 S: PlanningSolution,
27 M: Move<S>,
28{
29 fn pick_move(
33 &self,
34 placement: &Placement<S, M>,
35 score_director: &mut dyn ScoreDirector<S>,
36 ) -> Option<M>;
37}
38
39#[derive(Clone, Default)]
44pub struct FirstFitForager<S, M> {
45 _phantom: PhantomData<(S, M)>,
46}
47
48impl<S, M> Debug for FirstFitForager<S, M> {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("FirstFitForager").finish()
51 }
52}
53
54impl<S, M> FirstFitForager<S, M> {
55 pub fn new() -> Self {
57 Self {
58 _phantom: PhantomData,
59 }
60 }
61}
62
63impl<S, M> ConstructionForager<S, M> for FirstFitForager<S, M>
64where
65 S: PlanningSolution,
66 M: Move<S>,
67{
68 fn pick_move(
69 &self,
70 placement: &Placement<S, M>,
71 score_director: &mut dyn ScoreDirector<S>,
72 ) -> Option<M> {
73 for m in &placement.moves {
75 if m.is_doable(score_director) {
76 return Some(m.clone());
77 }
78 }
79 None
80 }
81}
82
83#[derive(Clone, Default)]
89pub struct BestFitForager<S, M> {
90 _phantom: PhantomData<(S, M)>,
91}
92
93impl<S, M> Debug for BestFitForager<S, M> {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.debug_struct("BestFitForager").finish()
96 }
97}
98
99impl<S, M> BestFitForager<S, M> {
100 pub fn new() -> Self {
102 Self {
103 _phantom: PhantomData,
104 }
105 }
106}
107
108impl<S, M> ConstructionForager<S, M> for BestFitForager<S, M>
109where
110 S: PlanningSolution,
111 M: Move<S>,
112{
113 fn pick_move(
114 &self,
115 placement: &Placement<S, M>,
116 score_director: &mut dyn ScoreDirector<S>,
117 ) -> Option<M> {
118 let mut best_move: Option<M> = None;
119 let mut best_score: Option<S::Score> = None;
120
121 for m in &placement.moves {
122 if !m.is_doable(score_director) {
123 continue;
124 }
125
126 let score = {
128 let mut recording = RecordingScoreDirector::new(score_director);
129
130 m.do_move(&mut recording);
132
133 let score = recording.calculate_score();
135
136 recording.undo_changes();
138
139 score
140 };
141
142 let is_better = match &best_score {
144 None => true,
145 Some(best) => score > *best,
146 };
147
148 if is_better {
149 best_move = Some(m.clone());
150 best_score = Some(score);
151 }
152 }
153
154 best_move
155 }
156}
157
158#[derive(Clone, Default)]
163pub struct FirstFeasibleForager<S, M> {
164 _phantom: PhantomData<(S, M)>,
165}
166
167impl<S, M> Debug for FirstFeasibleForager<S, M> {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 f.debug_struct("FirstFeasibleForager").finish()
170 }
171}
172
173impl<S, M> FirstFeasibleForager<S, M> {
174 pub fn new() -> Self {
176 Self {
177 _phantom: PhantomData,
178 }
179 }
180}
181
182impl<S, M> ConstructionForager<S, M> for FirstFeasibleForager<S, M>
183where
184 S: PlanningSolution,
185 M: Move<S>,
186{
187 fn pick_move(
188 &self,
189 placement: &Placement<S, M>,
190 score_director: &mut dyn ScoreDirector<S>,
191 ) -> Option<M> {
192 let mut fallback_move: Option<M> = None;
193 let mut fallback_score: Option<S::Score> = None;
194
195 for m in &placement.moves {
196 if !m.is_doable(score_director) {
197 continue;
198 }
199
200 let score = {
202 let mut recording = RecordingScoreDirector::new(score_director);
203
204 m.do_move(&mut recording);
206
207 let score = recording.calculate_score();
209
210 if score.is_feasible() {
212 recording.undo_changes();
213 return Some(m.clone());
214 }
215
216 recording.undo_changes();
218
219 score
220 };
221
222 let is_better = match &fallback_score {
224 None => true,
225 Some(best) => score > *best,
226 };
227
228 if is_better {
229 fallback_move = Some(m.clone());
230 fallback_score = Some(score);
231 }
232 }
233
234 fallback_move
236 }
237}
238
239pub struct WeakestFitForager<S, M>
273where
274 S: PlanningSolution,
275 M: Move<S>,
276{
277 strength_fn: fn(&M) -> i64,
279 _phantom: PhantomData<S>,
280}
281
282impl<S, M> Debug for WeakestFitForager<S, M>
283where
284 S: PlanningSolution,
285 M: Move<S>,
286{
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 f.debug_struct("WeakestFitForager").finish()
289 }
290}
291
292impl<S, M> WeakestFitForager<S, M>
293where
294 S: PlanningSolution,
295 M: Move<S>,
296{
297 pub fn new(strength_fn: fn(&M) -> i64) -> Self {
302 Self {
303 strength_fn,
304 _phantom: PhantomData,
305 }
306 }
307}
308
309impl<S, M> ConstructionForager<S, M> for WeakestFitForager<S, M>
310where
311 S: PlanningSolution,
312 M: Move<S>,
313{
314 fn pick_move(
315 &self,
316 placement: &Placement<S, M>,
317 score_director: &mut dyn ScoreDirector<S>,
318 ) -> Option<M> {
319 let mut best_move: Option<M> = None;
320 let mut min_strength: Option<i64> = None;
321
322 for m in &placement.moves {
323 if !m.is_doable(score_director) {
324 continue;
325 }
326
327 let strength = (self.strength_fn)(m);
328
329 let is_weaker = match min_strength {
330 None => true,
331 Some(best) => strength < best,
332 };
333
334 if is_weaker {
335 best_move = Some(m.clone());
336 min_strength = Some(strength);
337 }
338 }
339
340 best_move
341 }
342}
343
344pub struct StrongestFitForager<S, M>
378where
379 S: PlanningSolution,
380 M: Move<S>,
381{
382 strength_fn: fn(&M) -> i64,
384 _phantom: PhantomData<S>,
385}
386
387impl<S, M> Debug for StrongestFitForager<S, M>
388where
389 S: PlanningSolution,
390 M: Move<S>,
391{
392 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393 f.debug_struct("StrongestFitForager").finish()
394 }
395}
396
397impl<S, M> StrongestFitForager<S, M>
398where
399 S: PlanningSolution,
400 M: Move<S>,
401{
402 pub fn new(strength_fn: fn(&M) -> i64) -> Self {
407 Self {
408 strength_fn,
409 _phantom: PhantomData,
410 }
411 }
412}
413
414impl<S, M> ConstructionForager<S, M> for StrongestFitForager<S, M>
415where
416 S: PlanningSolution,
417 M: Move<S>,
418{
419 fn pick_move(
420 &self,
421 placement: &Placement<S, M>,
422 score_director: &mut dyn ScoreDirector<S>,
423 ) -> Option<M> {
424 let mut best_move: Option<M> = None;
425 let mut max_strength: Option<i64> = None;
426
427 for m in &placement.moves {
428 if !m.is_doable(score_director) {
429 continue;
430 }
431
432 let strength = (self.strength_fn)(m);
433
434 let is_stronger = match max_strength {
435 None => true,
436 Some(best) => strength > best,
437 };
438
439 if is_stronger {
440 best_move = Some(m.clone());
441 max_strength = Some(strength);
442 }
443 }
444
445 best_move
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use crate::heuristic::r#move::ChangeMove;
453 use crate::heuristic::selector::EntityReference;
454 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
455 use solverforge_core::score::SimpleScore;
456 use solverforge_scoring::SimpleScoreDirector;
457 use std::any::TypeId;
458
459 #[derive(Clone, Debug)]
460 struct Queen {
461 row: Option<i64>,
462 }
463
464 #[derive(Clone, Debug)]
465 struct NQueensSolution {
466 queens: Vec<Queen>,
467 score: Option<SimpleScore>,
468 }
469
470 impl PlanningSolution for NQueensSolution {
471 type Score = SimpleScore;
472
473 fn score(&self) -> Option<Self::Score> {
474 self.score
475 }
476
477 fn set_score(&mut self, score: Option<Self::Score>) {
478 self.score = score;
479 }
480 }
481
482 fn get_queens(s: &NQueensSolution) -> &Vec<Queen> {
483 &s.queens
484 }
485
486 fn get_queens_mut(s: &mut NQueensSolution) -> &mut Vec<Queen> {
487 &mut s.queens
488 }
489
490 fn get_queen_row(s: &NQueensSolution, idx: usize) -> Option<i64> {
492 s.queens.get(idx).and_then(|q| q.row)
493 }
494
495 fn set_queen_row(s: &mut NQueensSolution, idx: usize, v: Option<i64>) {
497 if let Some(queen) = s.queens.get_mut(idx) {
498 queen.row = v;
499 }
500 }
501
502 fn create_test_director(
503 ) -> SimpleScoreDirector<NQueensSolution, impl Fn(&NQueensSolution) -> SimpleScore> {
504 let solution = NQueensSolution {
505 queens: vec![Queen { row: None }],
506 score: None,
507 };
508
509 let extractor = Box::new(TypedEntityExtractor::new(
510 "Queen",
511 "queens",
512 get_queens,
513 get_queens_mut,
514 ));
515 let entity_desc = EntityDescriptor::new("Queen", TypeId::of::<Queen>(), "queens")
516 .with_extractor(extractor);
517
518 let descriptor =
519 SolutionDescriptor::new("NQueensSolution", TypeId::of::<NQueensSolution>())
520 .with_entity(entity_desc);
521
522 SimpleScoreDirector::with_calculator(solution, descriptor, |sol| {
524 let sum: i64 = sol.queens.iter().filter_map(|q| q.row).sum();
525 SimpleScore::of(sum)
526 })
527 }
528
529 type TestMove = ChangeMove<NQueensSolution, i64>;
530
531 fn create_placement() -> Placement<NQueensSolution, TestMove> {
532 let entity_ref = EntityReference::new(0, 0);
533 let moves: Vec<TestMove> = vec![
534 ChangeMove::new(0, Some(1i64), get_queen_row, set_queen_row, "row", 0),
535 ChangeMove::new(0, Some(5i64), get_queen_row, set_queen_row, "row", 0),
536 ChangeMove::new(0, Some(3i64), get_queen_row, set_queen_row, "row", 0),
537 ];
538 Placement::new(entity_ref, moves)
539 }
540
541 #[test]
542 fn test_first_fit_forager() {
543 let mut director = create_test_director();
544 let placement = create_placement();
545
546 let forager = FirstFitForager::<NQueensSolution, TestMove>::new();
547 let selected = forager.pick_move(&placement, &mut director);
548
549 assert!(selected.is_some());
551 }
552
553 #[test]
554 fn test_best_fit_forager() {
555 let mut director = create_test_director();
556 let placement = create_placement();
557
558 let forager = BestFitForager::<NQueensSolution, TestMove>::new();
559 let selected = forager.pick_move(&placement, &mut director);
560
561 assert!(selected.is_some());
563
564 if let Some(m) = selected {
566 m.do_move(&mut director);
567 let score = director.calculate_score();
568 assert_eq!(score, SimpleScore::of(5));
569 }
570 }
571
572 #[test]
573 fn test_empty_placement() {
574 let mut director = create_test_director();
575 let placement = Placement::new(EntityReference::new(0, 0), vec![]);
576
577 let forager = FirstFitForager::<NQueensSolution, TestMove>::new();
578 let selected = forager.pick_move(&placement, &mut director);
579
580 assert!(selected.is_none());
581 }
582
583 fn value_strength(m: &TestMove) -> i64 {
584 m.to_value().copied().unwrap_or(0)
585 }
586
587 #[test]
588 fn test_weakest_fit_forager() {
589 let mut director = create_test_director();
590 let placement = create_placement(); let forager = WeakestFitForager::<NQueensSolution, TestMove>::new(value_strength);
593 let selected = forager.pick_move(&placement, &mut director);
594
595 assert!(selected.is_some());
597 if let Some(m) = selected {
598 m.do_move(&mut director);
599 let score = director.calculate_score();
600 assert_eq!(score, SimpleScore::of(1));
601 }
602 }
603
604 #[test]
605 fn test_strongest_fit_forager() {
606 let mut director = create_test_director();
607 let placement = create_placement(); let forager = StrongestFitForager::<NQueensSolution, TestMove>::new(value_strength);
610 let selected = forager.pick_move(&placement, &mut director);
611
612 assert!(selected.is_some());
614 if let Some(m) = selected {
615 m.do_move(&mut director);
616 let score = director.calculate_score();
617 assert_eq!(score, SimpleScore::of(5));
618 }
619 }
620}