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}