1use std::fmt::Debug;
13use std::marker::PhantomData;
14
15use solverforge_core::domain::PlanningSolution;
16use solverforge_core::score::Score;
17use solverforge_scoring::Director;
18
19use crate::heuristic::r#move::Move;
20
21use super::decision::{
22 is_first_fit_improvement, select_best_fit, select_first_feasible, select_first_fit,
23 ScoredChoiceTracker,
24};
25use super::evaluation::evaluate_trial_move;
26use super::Placement;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum ConstructionChoice {
31 KeepCurrent,
32 Select(usize),
33}
34
35pub trait ConstructionForager<S, M>: Send + Debug
44where
45 S: PlanningSolution,
46 M: Move<S>,
47{
48 fn pick_move_index<D: Director<S>>(
51 &self,
52 placement: &Placement<S, M>,
53 score_director: &mut D,
54 ) -> ConstructionChoice;
55}
56
57pub struct FirstFitForager<S, M> {
62 _phantom: PhantomData<fn() -> (S, M)>,
63}
64
65impl<S, M> Clone for FirstFitForager<S, M> {
66 fn clone(&self) -> Self {
67 *self
68 }
69}
70
71impl<S, M> Copy for FirstFitForager<S, M> {}
72
73impl<S, M> Default for FirstFitForager<S, M> {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl<S, M> Debug for FirstFitForager<S, M> {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.debug_struct("FirstFitForager").finish()
82 }
83}
84
85impl<S, M> FirstFitForager<S, M> {
86 pub fn new() -> Self {
87 Self {
88 _phantom: PhantomData,
89 }
90 }
91}
92
93impl<S, M> ConstructionForager<S, M> for FirstFitForager<S, M>
94where
95 S: PlanningSolution,
96 M: Move<S>,
97{
98 fn pick_move_index<D: Director<S>>(
99 &self,
100 placement: &Placement<S, M>,
101 score_director: &mut D,
102 ) -> ConstructionChoice {
103 let mut first_doable = None;
104 let baseline_score = placement
105 .keep_current_legal()
106 .then(|| score_director.calculate_score());
107
108 for (idx, m) in placement.moves.iter().enumerate() {
109 if !m.is_doable(score_director) {
110 continue;
111 }
112
113 if let Some(baseline_score) = baseline_score {
114 let candidate_score = evaluate_trial_move(score_director, m);
115 if is_first_fit_improvement(baseline_score, candidate_score) {
116 first_doable = Some(idx);
117 break;
118 }
119 } else {
120 first_doable = Some(idx);
121 break;
122 }
123 }
124
125 select_first_fit(first_doable)
126 }
127}
128
129pub struct BestFitForager<S, M> {
135 _phantom: PhantomData<fn() -> (S, M)>,
136}
137
138impl<S, M> Clone for BestFitForager<S, M> {
139 fn clone(&self) -> Self {
140 *self
141 }
142}
143
144impl<S, M> Copy for BestFitForager<S, M> {}
145
146impl<S, M> Default for BestFitForager<S, M> {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152impl<S, M> Debug for BestFitForager<S, M> {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 f.debug_struct("BestFitForager").finish()
155 }
156}
157
158impl<S, M> BestFitForager<S, M> {
159 pub fn new() -> Self {
160 Self {
161 _phantom: PhantomData,
162 }
163 }
164}
165
166impl<S, M> ConstructionForager<S, M> for BestFitForager<S, M>
167where
168 S: PlanningSolution,
169 M: Move<S>,
170{
171 fn pick_move_index<D: Director<S>>(
172 &self,
173 placement: &Placement<S, M>,
174 score_director: &mut D,
175 ) -> ConstructionChoice {
176 let baseline_score = placement
177 .keep_current_legal()
178 .then(|| score_director.calculate_score());
179 let mut tracker = ScoredChoiceTracker::default();
180
181 for (idx, m) in placement.moves.iter().enumerate() {
182 if !m.is_doable(score_director) {
183 continue;
184 }
185
186 let score = evaluate_trial_move(score_director, m);
187
188 tracker.consider(idx, score);
189 }
190
191 select_best_fit(tracker, baseline_score)
192 }
193}
194
195pub struct FirstFeasibleForager<S, M> {
200 _phantom: PhantomData<fn() -> (S, M)>,
201}
202
203impl<S, M> Clone for FirstFeasibleForager<S, M> {
204 fn clone(&self) -> Self {
205 *self
206 }
207}
208
209impl<S, M> Copy for FirstFeasibleForager<S, M> {}
210
211impl<S, M> Default for FirstFeasibleForager<S, M> {
212 fn default() -> Self {
213 Self::new()
214 }
215}
216
217impl<S, M> Debug for FirstFeasibleForager<S, M> {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 f.debug_struct("FirstFeasibleForager").finish()
220 }
221}
222
223impl<S, M> FirstFeasibleForager<S, M> {
224 pub fn new() -> Self {
225 Self {
226 _phantom: PhantomData,
227 }
228 }
229}
230
231impl<S, M> ConstructionForager<S, M> for FirstFeasibleForager<S, M>
232where
233 S: PlanningSolution,
234 M: Move<S>,
235{
236 fn pick_move_index<D: Director<S>>(
237 &self,
238 placement: &Placement<S, M>,
239 score_director: &mut D,
240 ) -> ConstructionChoice {
241 let baseline_score = placement
242 .keep_current_legal()
243 .then(|| score_director.calculate_score());
244
245 let mut fallback_tracker = ScoredChoiceTracker::default();
246 let mut first_feasible = None;
247
248 for (idx, m) in placement.moves.iter().enumerate() {
249 if !m.is_doable(score_director) {
250 continue;
251 }
252
253 let score = evaluate_trial_move(score_director, m);
254
255 if score.is_feasible() {
256 first_feasible = Some(idx);
257 break;
258 }
259
260 fallback_tracker.consider(idx, score);
261 }
262
263 select_first_feasible(first_feasible, fallback_tracker, baseline_score)
264 }
265}
266
267pub struct WeakestFitForager<S, M> {
273 strength_fn: fn(&M, &S) -> i64,
275 _phantom: PhantomData<fn() -> S>,
276}
277
278impl<S, M> Clone for WeakestFitForager<S, M> {
279 fn clone(&self) -> Self {
280 *self
281 }
282}
283
284impl<S, M> Copy for WeakestFitForager<S, M> {}
285
286impl<S, M> Debug for WeakestFitForager<S, M> {
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> {
293 pub fn new(strength_fn: fn(&M, &S) -> i64) -> Self {
298 Self {
299 strength_fn,
300 _phantom: PhantomData,
301 }
302 }
303
304 pub(crate) fn strength(&self, mov: &M, solution: &S) -> i64 {
305 (self.strength_fn)(mov, solution)
306 }
307}
308
309impl<S, M> ConstructionForager<S, M> for WeakestFitForager<S, M>
310where
311 S: PlanningSolution,
312 S::Score: Score,
313 M: Move<S>,
314{
315 fn pick_move_index<D: Director<S>>(
316 &self,
317 placement: &Placement<S, M>,
318 score_director: &mut D,
319 ) -> ConstructionChoice {
320 let mut best_idx: Option<usize> = None;
321 let mut min_strength: Option<i64> = None;
322
323 for (idx, m) in placement.moves.iter().enumerate() {
324 if !m.is_doable(score_director) {
325 continue;
326 }
327
328 let strength = self.strength(m, score_director.working_solution());
329
330 let is_weaker = match min_strength {
331 None => true,
332 Some(best) => strength < best,
333 };
334
335 if is_weaker {
336 best_idx = Some(idx);
337 min_strength = Some(strength);
338 }
339 }
340
341 let Some(best_idx) = best_idx else {
342 return ConstructionChoice::KeepCurrent;
343 };
344
345 if !placement.keep_current_legal() {
346 return ConstructionChoice::Select(best_idx);
347 }
348
349 let baseline_score = score_director.calculate_score();
350 let candidate_score = evaluate_trial_move(score_director, &placement.moves[best_idx]);
351 if candidate_score > baseline_score {
352 ConstructionChoice::Select(best_idx)
353 } else {
354 ConstructionChoice::KeepCurrent
355 }
356 }
357}
358
359pub struct StrongestFitForager<S, M> {
365 strength_fn: fn(&M, &S) -> i64,
367 _phantom: PhantomData<fn() -> S>,
368}
369
370impl<S, M> Clone for StrongestFitForager<S, M> {
371 fn clone(&self) -> Self {
372 *self
373 }
374}
375
376impl<S, M> Copy for StrongestFitForager<S, M> {}
377
378impl<S, M> Debug for StrongestFitForager<S, M> {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 f.debug_struct("StrongestFitForager").finish()
381 }
382}
383
384impl<S, M> StrongestFitForager<S, M> {
385 pub fn new(strength_fn: fn(&M, &S) -> i64) -> Self {
390 Self {
391 strength_fn,
392 _phantom: PhantomData,
393 }
394 }
395
396 pub(crate) fn strength(&self, mov: &M, solution: &S) -> i64 {
397 (self.strength_fn)(mov, solution)
398 }
399}
400
401impl<S, M> ConstructionForager<S, M> for StrongestFitForager<S, M>
402where
403 S: PlanningSolution,
404 S::Score: Score,
405 M: Move<S>,
406{
407 fn pick_move_index<D: Director<S>>(
408 &self,
409 placement: &Placement<S, M>,
410 score_director: &mut D,
411 ) -> ConstructionChoice {
412 let mut best_idx: Option<usize> = None;
413 let mut max_strength: Option<i64> = None;
414
415 for (idx, m) in placement.moves.iter().enumerate() {
416 if !m.is_doable(score_director) {
417 continue;
418 }
419
420 let strength = self.strength(m, score_director.working_solution());
421
422 let is_stronger = match max_strength {
423 None => true,
424 Some(best) => strength > best,
425 };
426
427 if is_stronger {
428 best_idx = Some(idx);
429 max_strength = Some(strength);
430 }
431 }
432
433 let Some(best_idx) = best_idx else {
434 return ConstructionChoice::KeepCurrent;
435 };
436
437 if !placement.keep_current_legal() {
438 return ConstructionChoice::Select(best_idx);
439 }
440
441 let baseline_score = score_director.calculate_score();
442 let candidate_score = evaluate_trial_move(score_director, &placement.moves[best_idx]);
443 if candidate_score > baseline_score {
444 ConstructionChoice::Select(best_idx)
445 } else {
446 ConstructionChoice::KeepCurrent
447 }
448 }
449}
450
451#[cfg(test)]
452mod tests;