1use std::fmt::Debug;
12use std::marker::PhantomData;
13
14use solverforge_core::domain::PlanningSolution;
15use solverforge_core::score::Score;
16use solverforge_scoring::{RecordingScoreDirector, ScoreDirector};
17
18use crate::heuristic::r#move::Move;
19
20use super::Placement;
21
22pub trait ConstructionForager<S, M>: Send + Debug
31where
32 S: PlanningSolution,
33 M: Move<S>,
34{
35 fn pick_move_index<D: ScoreDirector<S>>(
39 &self,
40 placement: &Placement<S, M>,
41 score_director: &mut D,
42 ) -> Option<usize>;
43}
44
45pub struct FirstFitForager<S, M> {
50 _phantom: PhantomData<fn() -> (S, M)>,
51}
52
53impl<S, M> Clone for FirstFitForager<S, M> {
54 fn clone(&self) -> Self {
55 *self
56 }
57}
58
59impl<S, M> Copy for FirstFitForager<S, M> {}
60
61impl<S, M> Default for FirstFitForager<S, M> {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl<S, M> Debug for FirstFitForager<S, M> {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 f.debug_struct("FirstFitForager").finish()
70 }
71}
72
73impl<S, M> FirstFitForager<S, M> {
74 pub fn new() -> Self {
76 Self {
77 _phantom: PhantomData,
78 }
79 }
80}
81
82impl<S, M> ConstructionForager<S, M> for FirstFitForager<S, M>
83where
84 S: PlanningSolution,
85 M: Move<S>,
86{
87 fn pick_move_index<D: ScoreDirector<S>>(
88 &self,
89 placement: &Placement<S, M>,
90 score_director: &mut D,
91 ) -> Option<usize> {
92 for (idx, m) in placement.moves.iter().enumerate() {
93 if m.is_doable(score_director) {
94 return Some(idx);
95 }
96 }
97 None
98 }
99}
100
101pub struct BestFitForager<S, M> {
107 _phantom: PhantomData<fn() -> (S, M)>,
108}
109
110impl<S, M> Clone for BestFitForager<S, M> {
111 fn clone(&self) -> Self {
112 *self
113 }
114}
115
116impl<S, M> Copy for BestFitForager<S, M> {}
117
118impl<S, M> Default for BestFitForager<S, M> {
119 fn default() -> Self {
120 Self::new()
121 }
122}
123
124impl<S, M> Debug for BestFitForager<S, M> {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("BestFitForager").finish()
127 }
128}
129
130impl<S, M> BestFitForager<S, M> {
131 pub fn new() -> Self {
133 Self {
134 _phantom: PhantomData,
135 }
136 }
137}
138
139impl<S, M> ConstructionForager<S, M> for BestFitForager<S, M>
140where
141 S: PlanningSolution,
142 M: Move<S>,
143{
144 fn pick_move_index<D: ScoreDirector<S>>(
145 &self,
146 placement: &Placement<S, M>,
147 score_director: &mut D,
148 ) -> Option<usize> {
149 let mut best_idx: Option<usize> = None;
150 let mut best_score: Option<S::Score> = None;
151
152 for (idx, m) in placement.moves.iter().enumerate() {
153 if !m.is_doable(score_director) {
154 continue;
155 }
156
157 let score = {
159 let mut recording = RecordingScoreDirector::new(score_director);
160
161 m.do_move(&mut recording);
163
164 let score = recording.calculate_score();
166
167 recording.undo_changes();
169
170 score
171 };
172
173 let is_better = match &best_score {
175 None => true,
176 Some(best) => score > *best,
177 };
178
179 if is_better {
180 best_idx = Some(idx);
181 best_score = Some(score);
182 }
183 }
184
185 best_idx
186 }
187}
188
189pub struct FirstFeasibleForager<S, M> {
194 _phantom: PhantomData<fn() -> (S, M)>,
195}
196
197impl<S, M> Clone for FirstFeasibleForager<S, M> {
198 fn clone(&self) -> Self {
199 *self
200 }
201}
202
203impl<S, M> Copy for FirstFeasibleForager<S, M> {}
204
205impl<S, M> Default for FirstFeasibleForager<S, M> {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211impl<S, M> Debug for FirstFeasibleForager<S, M> {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 f.debug_struct("FirstFeasibleForager").finish()
214 }
215}
216
217impl<S, M> FirstFeasibleForager<S, M> {
218 pub fn new() -> Self {
220 Self {
221 _phantom: PhantomData,
222 }
223 }
224}
225
226impl<S, M> ConstructionForager<S, M> for FirstFeasibleForager<S, M>
227where
228 S: PlanningSolution,
229 M: Move<S>,
230{
231 fn pick_move_index<D: ScoreDirector<S>>(
232 &self,
233 placement: &Placement<S, M>,
234 score_director: &mut D,
235 ) -> Option<usize> {
236 let mut fallback_idx: Option<usize> = None;
237 let mut fallback_score: Option<S::Score> = None;
238
239 for (idx, m) in placement.moves.iter().enumerate() {
240 if !m.is_doable(score_director) {
241 continue;
242 }
243
244 let score = {
246 let mut recording = RecordingScoreDirector::new(score_director);
247
248 m.do_move(&mut recording);
250
251 let score = recording.calculate_score();
253
254 if score.is_feasible() {
256 recording.undo_changes();
257 return Some(idx);
258 }
259
260 recording.undo_changes();
262
263 score
264 };
265
266 let is_better = match &fallback_score {
268 None => true,
269 Some(best) => score > *best,
270 };
271
272 if is_better {
273 fallback_idx = Some(idx);
274 fallback_score = Some(score);
275 }
276 }
277
278 fallback_idx
280 }
281}
282
283pub struct WeakestFitForager<S, M> {
289 strength_fn: fn(&M) -> i64,
291 _phantom: PhantomData<fn() -> S>,
292}
293
294impl<S, M> Clone for WeakestFitForager<S, M> {
295 fn clone(&self) -> Self {
296 *self
297 }
298}
299
300impl<S, M> Copy for WeakestFitForager<S, M> {}
301
302impl<S, M> Debug for WeakestFitForager<S, M> {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 f.debug_struct("WeakestFitForager").finish()
305 }
306}
307
308impl<S, M> WeakestFitForager<S, M> {
309 pub fn new(strength_fn: fn(&M) -> i64) -> Self {
314 Self {
315 strength_fn,
316 _phantom: PhantomData,
317 }
318 }
319}
320
321impl<S, M> ConstructionForager<S, M> for WeakestFitForager<S, M>
322where
323 S: PlanningSolution,
324 M: Move<S>,
325{
326 fn pick_move_index<D: ScoreDirector<S>>(
327 &self,
328 placement: &Placement<S, M>,
329 score_director: &mut D,
330 ) -> Option<usize> {
331 let mut best_idx: Option<usize> = None;
332 let mut min_strength: Option<i64> = None;
333
334 for (idx, m) in placement.moves.iter().enumerate() {
335 if !m.is_doable(score_director) {
336 continue;
337 }
338
339 let strength = (self.strength_fn)(m);
340
341 let is_weaker = match min_strength {
342 None => true,
343 Some(best) => strength < best,
344 };
345
346 if is_weaker {
347 best_idx = Some(idx);
348 min_strength = Some(strength);
349 }
350 }
351
352 best_idx
353 }
354}
355
356pub struct StrongestFitForager<S, M> {
362 strength_fn: fn(&M) -> i64,
364 _phantom: PhantomData<fn() -> S>,
365}
366
367impl<S, M> Clone for StrongestFitForager<S, M> {
368 fn clone(&self) -> Self {
369 *self
370 }
371}
372
373impl<S, M> Copy for StrongestFitForager<S, M> {}
374
375impl<S, M> Debug for StrongestFitForager<S, M> {
376 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377 f.debug_struct("StrongestFitForager").finish()
378 }
379}
380
381impl<S, M> StrongestFitForager<S, M> {
382 pub fn new(strength_fn: fn(&M) -> i64) -> Self {
387 Self {
388 strength_fn,
389 _phantom: PhantomData,
390 }
391 }
392}
393
394impl<S, M> ConstructionForager<S, M> for StrongestFitForager<S, M>
395where
396 S: PlanningSolution,
397 M: Move<S>,
398{
399 fn pick_move_index<D: ScoreDirector<S>>(
400 &self,
401 placement: &Placement<S, M>,
402 score_director: &mut D,
403 ) -> Option<usize> {
404 let mut best_idx: Option<usize> = None;
405 let mut max_strength: Option<i64> = None;
406
407 for (idx, m) in placement.moves.iter().enumerate() {
408 if !m.is_doable(score_director) {
409 continue;
410 }
411
412 let strength = (self.strength_fn)(m);
413
414 let is_stronger = match max_strength {
415 None => true,
416 Some(best) => strength > best,
417 };
418
419 if is_stronger {
420 best_idx = Some(idx);
421 max_strength = Some(strength);
422 }
423 }
424
425 best_idx
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432 use crate::heuristic::r#move::ChangeMove;
433 use crate::heuristic::selector::EntityReference;
434 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
435 use solverforge_core::score::SimpleScore;
436 use solverforge_scoring::SimpleScoreDirector;
437 use std::any::TypeId;
438
439 #[derive(Clone, Debug)]
440 struct Queen {
441 row: Option<i64>,
442 }
443
444 #[derive(Clone, Debug)]
445 struct NQueensSolution {
446 queens: Vec<Queen>,
447 score: Option<SimpleScore>,
448 }
449
450 impl PlanningSolution for NQueensSolution {
451 type Score = SimpleScore;
452
453 fn score(&self) -> Option<Self::Score> {
454 self.score
455 }
456
457 fn set_score(&mut self, score: Option<Self::Score>) {
458 self.score = score;
459 }
460 }
461
462 fn get_queens(s: &NQueensSolution) -> &Vec<Queen> {
463 &s.queens
464 }
465
466 fn get_queens_mut(s: &mut NQueensSolution) -> &mut Vec<Queen> {
467 &mut s.queens
468 }
469
470 fn get_queen_row(s: &NQueensSolution, idx: usize) -> Option<i64> {
471 s.queens.get(idx).and_then(|q| q.row)
472 }
473
474 fn set_queen_row(s: &mut NQueensSolution, idx: usize, v: Option<i64>) {
475 if let Some(queen) = s.queens.get_mut(idx) {
476 queen.row = v;
477 }
478 }
479
480 fn create_test_director(
481 ) -> SimpleScoreDirector<NQueensSolution, impl Fn(&NQueensSolution) -> SimpleScore> {
482 let solution = NQueensSolution {
483 queens: vec![Queen { row: None }],
484 score: None,
485 };
486
487 let extractor = Box::new(TypedEntityExtractor::new(
488 "Queen",
489 "queens",
490 get_queens,
491 get_queens_mut,
492 ));
493 let entity_desc = EntityDescriptor::new("Queen", TypeId::of::<Queen>(), "queens")
494 .with_extractor(extractor);
495
496 let descriptor =
497 SolutionDescriptor::new("NQueensSolution", TypeId::of::<NQueensSolution>())
498 .with_entity(entity_desc);
499
500 SimpleScoreDirector::with_calculator(solution, descriptor, |sol| {
501 let sum: i64 = sol.queens.iter().filter_map(|q| q.row).sum();
502 SimpleScore::of(sum)
503 })
504 }
505
506 type TestMove = ChangeMove<NQueensSolution, i64>;
507
508 fn create_placement() -> Placement<NQueensSolution, TestMove> {
509 let entity_ref = EntityReference::new(0, 0);
510 let moves: Vec<TestMove> = vec![
511 ChangeMove::new(0, Some(1i64), get_queen_row, set_queen_row, "row", 0),
512 ChangeMove::new(0, Some(5i64), get_queen_row, set_queen_row, "row", 0),
513 ChangeMove::new(0, Some(3i64), get_queen_row, set_queen_row, "row", 0),
514 ];
515 Placement::new(entity_ref, moves)
516 }
517
518 #[test]
519 fn test_first_fit_forager() {
520 let mut director = create_test_director();
521 let mut placement = create_placement();
522
523 let forager = FirstFitForager::<NQueensSolution, TestMove>::new();
524 let selected_idx = forager.pick_move_index(&placement, &mut director);
525
526 assert_eq!(selected_idx, Some(0));
528
529 if let Some(idx) = selected_idx {
531 let m = placement.moves.swap_remove(idx);
532 m.do_move(&mut director);
533 }
534 }
535
536 #[test]
537 fn test_best_fit_forager() {
538 let mut director = create_test_director();
539 let mut placement = create_placement();
540
541 let forager = BestFitForager::<NQueensSolution, TestMove>::new();
542 let selected_idx = forager.pick_move_index(&placement, &mut director);
543
544 assert_eq!(selected_idx, Some(1));
546
547 if let Some(idx) = selected_idx {
549 let m = placement.moves.swap_remove(idx);
550 m.do_move(&mut director);
551 let score = director.calculate_score();
552 assert_eq!(score, SimpleScore::of(5));
553 }
554 }
555
556 #[test]
557 fn test_empty_placement() {
558 let mut director = create_test_director();
559 let placement: Placement<NQueensSolution, TestMove> =
560 Placement::new(EntityReference::new(0, 0), vec![]);
561
562 let forager = FirstFitForager::<NQueensSolution, TestMove>::new();
563 let selected_idx = forager.pick_move_index(&placement, &mut director);
564
565 assert!(selected_idx.is_none());
566 }
567
568 fn value_strength(m: &TestMove) -> i64 {
569 m.to_value().copied().unwrap_or(0)
570 }
571
572 #[test]
573 fn test_weakest_fit_forager() {
574 let mut director = create_test_director();
575 let mut placement = create_placement(); let forager = WeakestFitForager::<NQueensSolution, TestMove>::new(value_strength);
578 let selected_idx = forager.pick_move_index(&placement, &mut director);
579
580 assert_eq!(selected_idx, Some(0));
582
583 if let Some(idx) = selected_idx {
584 let m = placement.moves.swap_remove(idx);
585 m.do_move(&mut director);
586 let score = director.calculate_score();
587 assert_eq!(score, SimpleScore::of(1));
588 }
589 }
590
591 #[test]
592 fn test_strongest_fit_forager() {
593 let mut director = create_test_director();
594 let mut placement = create_placement(); let forager = StrongestFitForager::<NQueensSolution, TestMove>::new(value_strength);
597 let selected_idx = forager.pick_move_index(&placement, &mut director);
598
599 assert_eq!(selected_idx, Some(1));
601
602 if let Some(idx) = selected_idx {
603 let m = placement.moves.swap_remove(idx);
604 m.do_move(&mut director);
605 let score = director.calculate_score();
606 assert_eq!(score, SimpleScore::of(5));
607 }
608 }
609}