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