Skip to main content

solverforge_solver/phase/construction/
forager_impl.rs

1use solverforge_config::ConstructionObligation;
2use solverforge_core::domain::PlanningSolution;
3use solverforge_core::score::Score;
4use solverforge_scoring::Director;
5
6use super::decision::{
7    is_first_fit_improvement, select_best_fit, select_first_feasible, select_first_fit,
8    ScoredChoiceTracker,
9};
10use super::evaluation::evaluate_trial_move;
11use super::forager::{
12    BestFitForager, ConstructionChoice, ConstructionForager, FirstFeasibleForager, FirstFitForager,
13    StrongestFitForager, WeakestFitForager,
14};
15use super::forager_step::{
16    select_best_fit_index, select_first_feasible_index, select_first_fit_index,
17    select_strongest_fit_index, select_weakest_fit_index,
18};
19use super::Placement;
20use crate::heuristic::r#move::Move;
21use crate::scope::{ProgressCallback, StepScope};
22
23impl<S, M> ConstructionForager<S, M> for FirstFitForager<S, M>
24where
25    S: PlanningSolution,
26    M: Move<S>,
27{
28    fn pick_move_index<D: Director<S>>(
29        &self,
30        placement: &Placement<S, M>,
31        score_director: &mut D,
32    ) -> ConstructionChoice {
33        let mut first_doable = None;
34        let baseline_score = placement
35            .keep_current_legal()
36            .then(|| score_director.calculate_score());
37
38        for (idx, m) in placement.moves.iter().enumerate() {
39            if !m.is_doable(score_director) {
40                continue;
41            }
42
43            if let Some(baseline_score) = baseline_score {
44                let candidate_score = evaluate_trial_move(score_director, m);
45                if is_first_fit_improvement(baseline_score, candidate_score) {
46                    first_doable = Some(idx);
47                    break;
48                }
49            } else {
50                first_doable = Some(idx);
51                break;
52            }
53        }
54
55        select_first_fit(first_doable)
56    }
57
58    fn select_move_index<D, BestCb>(
59        &self,
60        placement: &Placement<S, M>,
61        construction_obligation: ConstructionObligation,
62        step_scope: &mut StepScope<'_, '_, '_, S, D, BestCb>,
63    ) -> Option<ConstructionChoice>
64    where
65        D: Director<S>,
66        BestCb: ProgressCallback<S>,
67    {
68        select_first_fit_index(placement, construction_obligation, step_scope)
69    }
70}
71
72impl<S, M> ConstructionForager<S, M> for BestFitForager<S, M>
73where
74    S: PlanningSolution,
75    M: Move<S>,
76{
77    fn pick_move_index<D: Director<S>>(
78        &self,
79        placement: &Placement<S, M>,
80        score_director: &mut D,
81    ) -> ConstructionChoice {
82        let baseline_score = placement
83            .keep_current_legal()
84            .then(|| score_director.calculate_score());
85        let mut tracker = ScoredChoiceTracker::default();
86
87        for (idx, m) in placement.moves.iter().enumerate() {
88            if !m.is_doable(score_director) {
89                continue;
90            }
91
92            let score = evaluate_trial_move(score_director, m);
93
94            tracker.consider(idx, score);
95        }
96
97        select_best_fit(tracker, baseline_score)
98    }
99
100    fn select_move_index<D, BestCb>(
101        &self,
102        placement: &Placement<S, M>,
103        construction_obligation: ConstructionObligation,
104        step_scope: &mut StepScope<'_, '_, '_, S, D, BestCb>,
105    ) -> Option<ConstructionChoice>
106    where
107        D: Director<S>,
108        BestCb: ProgressCallback<S>,
109    {
110        select_best_fit_index(placement, construction_obligation, step_scope)
111    }
112}
113
114impl<S, M> ConstructionForager<S, M> for FirstFeasibleForager<S, M>
115where
116    S: PlanningSolution,
117    M: Move<S>,
118{
119    fn pick_move_index<D: Director<S>>(
120        &self,
121        placement: &Placement<S, M>,
122        score_director: &mut D,
123    ) -> ConstructionChoice {
124        let baseline_score = placement
125            .keep_current_legal()
126            .then(|| score_director.calculate_score());
127
128        let mut fallback_tracker = ScoredChoiceTracker::default();
129        let mut first_feasible = None;
130
131        for (idx, m) in placement.moves.iter().enumerate() {
132            if !m.is_doable(score_director) {
133                continue;
134            }
135
136            let score = evaluate_trial_move(score_director, m);
137
138            if score.is_feasible() {
139                first_feasible = Some(idx);
140                break;
141            }
142
143            fallback_tracker.consider(idx, score);
144        }
145
146        select_first_feasible(first_feasible, fallback_tracker, baseline_score)
147    }
148
149    fn select_move_index<D, BestCb>(
150        &self,
151        placement: &Placement<S, M>,
152        construction_obligation: ConstructionObligation,
153        step_scope: &mut StepScope<'_, '_, '_, S, D, BestCb>,
154    ) -> Option<ConstructionChoice>
155    where
156        D: Director<S>,
157        BestCb: ProgressCallback<S>,
158    {
159        select_first_feasible_index(placement, construction_obligation, step_scope)
160    }
161}
162
163impl<S, M> ConstructionForager<S, M> for WeakestFitForager<S, M>
164where
165    S: PlanningSolution,
166    S::Score: Score,
167    M: Move<S>,
168{
169    fn pick_move_index<D: Director<S>>(
170        &self,
171        placement: &Placement<S, M>,
172        score_director: &mut D,
173    ) -> ConstructionChoice {
174        let mut best_idx: Option<usize> = None;
175        let mut min_strength: Option<i64> = None;
176
177        for (idx, m) in placement.moves.iter().enumerate() {
178            if !m.is_doable(score_director) {
179                continue;
180            }
181
182            let strength = self.strength(m, score_director.working_solution());
183
184            let is_weaker = match min_strength {
185                None => true,
186                Some(best) => strength < best,
187            };
188
189            if is_weaker {
190                best_idx = Some(idx);
191                min_strength = Some(strength);
192            }
193        }
194
195        let Some(best_idx) = best_idx else {
196            return ConstructionChoice::KeepCurrent;
197        };
198
199        if !placement.keep_current_legal() {
200            return ConstructionChoice::Select(best_idx);
201        }
202
203        let baseline_score = score_director.calculate_score();
204        let candidate_score = evaluate_trial_move(score_director, &placement.moves[best_idx]);
205        if candidate_score > baseline_score {
206            ConstructionChoice::Select(best_idx)
207        } else {
208            ConstructionChoice::KeepCurrent
209        }
210    }
211
212    fn select_move_index<D, BestCb>(
213        &self,
214        placement: &Placement<S, M>,
215        construction_obligation: ConstructionObligation,
216        step_scope: &mut StepScope<'_, '_, '_, S, D, BestCb>,
217    ) -> Option<ConstructionChoice>
218    where
219        D: Director<S>,
220        BestCb: ProgressCallback<S>,
221    {
222        select_weakest_fit_index(self, placement, construction_obligation, step_scope)
223    }
224}
225
226impl<S, M> ConstructionForager<S, M> for StrongestFitForager<S, M>
227where
228    S: PlanningSolution,
229    S::Score: Score,
230    M: Move<S>,
231{
232    fn pick_move_index<D: Director<S>>(
233        &self,
234        placement: &Placement<S, M>,
235        score_director: &mut D,
236    ) -> ConstructionChoice {
237        let mut best_idx: Option<usize> = None;
238        let mut max_strength: Option<i64> = None;
239
240        for (idx, m) in placement.moves.iter().enumerate() {
241            if !m.is_doable(score_director) {
242                continue;
243            }
244
245            let strength = self.strength(m, score_director.working_solution());
246
247            let is_stronger = match max_strength {
248                None => true,
249                Some(best) => strength > best,
250            };
251
252            if is_stronger {
253                best_idx = Some(idx);
254                max_strength = Some(strength);
255            }
256        }
257
258        let Some(best_idx) = best_idx else {
259            return ConstructionChoice::KeepCurrent;
260        };
261
262        if !placement.keep_current_legal() {
263            return ConstructionChoice::Select(best_idx);
264        }
265
266        let baseline_score = score_director.calculate_score();
267        let candidate_score = evaluate_trial_move(score_director, &placement.moves[best_idx]);
268        if candidate_score > baseline_score {
269            ConstructionChoice::Select(best_idx)
270        } else {
271            ConstructionChoice::KeepCurrent
272        }
273    }
274
275    fn select_move_index<D, BestCb>(
276        &self,
277        placement: &Placement<S, M>,
278        construction_obligation: ConstructionObligation,
279        step_scope: &mut StepScope<'_, '_, '_, S, D, BestCb>,
280    ) -> Option<ConstructionChoice>
281    where
282        D: Director<S>,
283        BestCb: ProgressCallback<S>,
284    {
285        select_strongest_fit_index(self, placement, construction_obligation, step_scope)
286    }
287}