Skip to main content

solverforge_solver/descriptor_scalar/
construction.rs

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}