1use std::any::Any;
2use std::fmt::{self, Debug};
3use std::marker::PhantomData;
4
5use solverforge_config::ConstructionHeuristicType;
6use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
7use solverforge_core::score::Score;
8use solverforge_scoring::Director;
9
10use crate::heuristic::selector::EntityReference;
11use crate::phase::construction::{
12 BestFitForager, ConstructionHeuristicPhase, EntityPlacer, FirstFitForager, Placement,
13 StrongestFitForager, WeakestFitForager,
14};
15use crate::scope::{ProgressCallback, SolverScope};
16
17use super::super::bindings::ResolvedVariableBinding;
18use super::super::move_types::{DescriptorChangeMove, DescriptorMoveUnion};
19
20pub enum DescriptorConstruction<S: PlanningSolution> {
21 FirstFit(
22 ConstructionHeuristicPhase<
23 S,
24 DescriptorMoveUnion<S>,
25 DescriptorEntityPlacer<S>,
26 FirstFitForager<S, DescriptorMoveUnion<S>>,
27 >,
28 ),
29 BestFit(
30 ConstructionHeuristicPhase<
31 S,
32 DescriptorMoveUnion<S>,
33 DescriptorEntityPlacer<S>,
34 BestFitForager<S, DescriptorMoveUnion<S>>,
35 >,
36 ),
37 WeakestFit(
38 ConstructionHeuristicPhase<
39 S,
40 DescriptorMoveUnion<S>,
41 DescriptorEntityPlacer<S>,
42 WeakestFitForager<S, DescriptorMoveUnion<S>>,
43 >,
44 ),
45 StrongestFit(
46 ConstructionHeuristicPhase<
47 S,
48 DescriptorMoveUnion<S>,
49 DescriptorEntityPlacer<S>,
50 StrongestFitForager<S, DescriptorMoveUnion<S>>,
51 >,
52 ),
53}
54
55impl<S: PlanningSolution> Debug for DescriptorConstruction<S> {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::FirstFit(phase) => write!(f, "DescriptorConstruction::FirstFit({phase:?})"),
59 Self::BestFit(phase) => write!(f, "DescriptorConstruction::BestFit({phase:?})"),
60 Self::WeakestFit(phase) => {
61 write!(f, "DescriptorConstruction::WeakestFit({phase:?})")
62 }
63 Self::StrongestFit(phase) => {
64 write!(f, "DescriptorConstruction::StrongestFit({phase:?})")
65 }
66 }
67 }
68}
69
70impl<S, D, ProgressCb> crate::phase::Phase<S, D, ProgressCb> for DescriptorConstruction<S>
71where
72 S: PlanningSolution + 'static,
73 D: Director<S>,
74 ProgressCb: ProgressCallback<S>,
75{
76 fn solve(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
77 match self {
78 Self::FirstFit(phase) => phase.solve(solver_scope),
79 Self::BestFit(phase) => phase.solve(solver_scope),
80 Self::WeakestFit(phase) => phase.solve(solver_scope),
81 Self::StrongestFit(phase) => phase.solve(solver_scope),
82 }
83 }
84
85 fn phase_type_name(&self) -> &'static str {
86 "DescriptorConstruction"
87 }
88}
89
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub(super) enum EntityOrder {
92 Canonical,
93 AscendingKey,
94 DescendingKey,
95}
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub(super) enum ValueOrder {
99 Canonical,
100 AscendingKey,
101}
102
103#[derive(Clone)]
104pub struct DescriptorEntityPlacer<S> {
105 bindings: Vec<ResolvedVariableBinding<S>>,
106 solution_descriptor: SolutionDescriptor,
107 entity_order: EntityOrder,
108 value_order: ValueOrder,
109 value_candidate_limit: Option<usize>,
110 _phantom: PhantomData<fn() -> S>,
111}
112
113impl<S> Debug for DescriptorEntityPlacer<S> {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 f.debug_struct("DescriptorEntityPlacer")
116 .field("bindings", &self.bindings)
117 .field("entity_order", &self.entity_order)
118 .field("value_order", &self.value_order)
119 .field("value_candidate_limit", &self.value_candidate_limit)
120 .finish()
121 }
122}
123
124impl<S> DescriptorEntityPlacer<S>
125where
126 S: PlanningSolution + 'static,
127{
128 pub(super) fn new(
129 bindings: Vec<ResolvedVariableBinding<S>>,
130 solution_descriptor: SolutionDescriptor,
131 entity_order: EntityOrder,
132 value_order: ValueOrder,
133 value_candidate_limit: Option<usize>,
134 ) -> Self {
135 Self {
136 bindings,
137 solution_descriptor,
138 entity_order,
139 value_order,
140 value_candidate_limit,
141 _phantom: PhantomData,
142 }
143 }
144
145 fn entity_order_key(
146 &self,
147 binding: &ResolvedVariableBinding<S>,
148 solution: &S,
149 entity_index: usize,
150 ) -> i64 {
151 binding.entity_order_key(solution, entity_index).unwrap_or_else(|| {
152 unreachable!(
153 "validated descriptor-driven construction must provide construction_entity_order_key for {}.{}",
154 binding.entity_type_name,
155 binding.variable_name
156 )
157 })
158 }
159
160 fn value_order_key(
161 &self,
162 binding: &ResolvedVariableBinding<S>,
163 solution: &S,
164 entity_index: usize,
165 value: usize,
166 ) -> i64 {
167 binding
168 .value_order_key(solution, entity_index, value)
169 .unwrap_or_else(|| {
170 unreachable!(
171 "validated descriptor-driven construction must provide construction_value_order_key for {}.{}",
172 binding.entity_type_name,
173 binding.variable_name
174 )
175 })
176 }
177
178 fn ordered_entity_indices<D: Director<S>>(
179 &self,
180 binding: &ResolvedVariableBinding<S>,
181 score_director: &D,
182 ) -> Vec<usize> {
183 let count = score_director
184 .entity_count(binding.descriptor_index)
185 .unwrap_or(0);
186 let mut entity_indices: Vec<_> = (0..count).collect();
187 if self.entity_order != EntityOrder::Canonical {
188 let solution = score_director.working_solution();
189 entity_indices.sort_by(|left, right| {
190 let left_key = self.entity_order_key(binding, solution, *left);
191 let right_key = self.entity_order_key(binding, solution, *right);
192 match self.entity_order {
193 EntityOrder::Canonical => left.cmp(right),
194 EntityOrder::AscendingKey => left_key.cmp(&right_key).then(left.cmp(right)),
195 EntityOrder::DescendingKey => right_key.cmp(&left_key).then(left.cmp(right)),
196 }
197 });
198 }
199 entity_indices
200 }
201
202 fn ordered_candidate_values(
203 &self,
204 binding: &ResolvedVariableBinding<S>,
205 solution: &S,
206 entity_index: usize,
207 ) -> Vec<usize> {
208 let mut values: Vec<_> = binding
209 .candidate_values_for_entity_index(
210 &self.solution_descriptor,
211 solution as &dyn Any,
212 entity_index,
213 self.value_candidate_limit,
214 )
215 .into_iter()
216 .enumerate()
217 .collect();
218 if self.value_order != ValueOrder::Canonical {
219 values.sort_by(|(left_order, left_value), (right_order, right_value)| {
220 let left_key = self.value_order_key(binding, solution, entity_index, *left_value);
221 let right_key = self.value_order_key(binding, solution, entity_index, *right_value);
222 match self.value_order {
223 ValueOrder::Canonical => left_order.cmp(right_order),
224 ValueOrder::AscendingKey => {
225 left_key.cmp(&right_key).then(left_order.cmp(right_order))
226 }
227 }
228 });
229 }
230 values.into_iter().map(|(_, value)| value).collect()
231 }
232
233 fn descriptor_change_move(
234 &self,
235 binding: &ResolvedVariableBinding<S>,
236 entity_index: usize,
237 value: usize,
238 ) -> DescriptorMoveUnion<S> {
239 let mut mov = DescriptorChangeMove::new(
240 binding.clone_binding(),
241 entity_index,
242 Some(value),
243 self.solution_descriptor.clone(),
244 );
245 if let Some(order_key) = binding.runtime_value_order_key() {
246 mov = mov.with_construction_value_order_key(order_key);
247 }
248 DescriptorMoveUnion::Change(mov)
249 }
250
251 fn placement_for_entity(
252 &self,
253 binding: &ResolvedVariableBinding<S>,
254 solution: &S,
255 entity_index: usize,
256 ) -> Option<Placement<S, DescriptorMoveUnion<S>>> {
257 let moves = self
258 .ordered_candidate_values(binding, solution, entity_index)
259 .into_iter()
260 .map(|value| self.descriptor_change_move(binding, entity_index, value))
261 .collect::<Vec<_>>();
262
263 if moves.is_empty() {
264 return None;
265 }
266
267 Some(
268 Placement::new(
269 EntityReference::new(binding.descriptor_index, entity_index),
270 moves,
271 )
272 .with_slot_id(binding.slot_id(entity_index))
273 .with_keep_current_legal(binding.allows_unassigned),
274 )
275 }
276}
277
278impl<S> EntityPlacer<S, DescriptorMoveUnion<S>> for DescriptorEntityPlacer<S>
279where
280 S: PlanningSolution + 'static,
281{
282 fn get_placements<D: Director<S>>(
283 &self,
284 score_director: &D,
285 ) -> Vec<Placement<S, DescriptorMoveUnion<S>>> {
286 let mut placements = Vec::new();
287 let solution = score_director.working_solution();
288 let erased_solution = solution as &dyn Any;
289
290 for binding in &self.bindings {
291 for entity_index in self.ordered_entity_indices(binding, score_director) {
292 let entity = self
293 .solution_descriptor
294 .get_entity(erased_solution, binding.descriptor_index, entity_index)
295 .expect("entity lookup failed for descriptor construction");
296 if (binding.getter)(entity).is_some() {
297 continue;
298 }
299
300 if let Some(placement) = self.placement_for_entity(binding, solution, entity_index)
301 {
302 placements.push(placement);
303 }
304 }
305 }
306
307 placements
308 }
309
310 fn get_next_placement<D, IsCompleted>(
311 &self,
312 score_director: &D,
313 mut is_completed: IsCompleted,
314 ) -> Option<(Placement<S, DescriptorMoveUnion<S>>, u64)>
315 where
316 D: Director<S>,
317 IsCompleted: FnMut(usize, usize) -> bool,
318 {
319 let solution = score_director.working_solution();
320 let erased_solution = solution as &dyn Any;
321
322 for binding in &self.bindings {
323 for entity_index in self.ordered_entity_indices(binding, score_director) {
324 let slot_id = binding.slot_id(entity_index);
325 if is_completed(slot_id.binding_index(), slot_id.entity_index()) {
326 continue;
327 }
328
329 let entity = self
330 .solution_descriptor
331 .get_entity(erased_solution, binding.descriptor_index, entity_index)
332 .expect("entity lookup failed for descriptor construction");
333 if (binding.getter)(entity).is_some() {
334 continue;
335 }
336
337 if let Some(placement) = self.placement_for_entity(binding, solution, entity_index)
338 {
339 let generated_moves = u64::try_from(placement.moves.len()).unwrap_or(u64::MAX);
340 return Some((placement, generated_moves));
341 }
342 }
343 }
344
345 None
346 }
347}
348
349pub(super) fn descriptor_move_strength<S>(mov: &DescriptorMoveUnion<S>, solution: &S) -> i64
350where
351 S: PlanningSolution + 'static,
352 S::Score: Score,
353{
354 match mov {
355 DescriptorMoveUnion::Change(mov) => mov.live_value_order_key(solution).unwrap_or(0),
356 _ => 0,
357 }
358}
359
360pub(super) fn entity_order_for_heuristic(heuristic: ConstructionHeuristicType) -> EntityOrder {
361 match heuristic {
362 ConstructionHeuristicType::FirstFitDecreasing
363 | ConstructionHeuristicType::WeakestFitDecreasing
364 | ConstructionHeuristicType::StrongestFitDecreasing => EntityOrder::DescendingKey,
365 ConstructionHeuristicType::AllocateEntityFromQueue => EntityOrder::AscendingKey,
366 _ => EntityOrder::Canonical,
367 }
368}
369
370pub(super) fn value_order_for_heuristic(heuristic: ConstructionHeuristicType) -> ValueOrder {
371 match heuristic {
372 ConstructionHeuristicType::AllocateToValueFromQueue => ValueOrder::AscendingKey,
373 _ => ValueOrder::Canonical,
374 }
375}
376
377pub(super) fn heuristic_requires_live_refresh(heuristic: ConstructionHeuristicType) -> bool {
378 matches!(
379 heuristic,
380 ConstructionHeuristicType::FirstFitDecreasing
381 | ConstructionHeuristicType::WeakestFit
382 | ConstructionHeuristicType::WeakestFitDecreasing
383 | ConstructionHeuristicType::StrongestFit
384 | ConstructionHeuristicType::StrongestFitDecreasing
385 | ConstructionHeuristicType::AllocateEntityFromQueue
386 | ConstructionHeuristicType::AllocateToValueFromQueue
387 )
388}