Skip to main content

solverforge_solver/
descriptor_standard.rs

1use std::any::Any;
2use std::fmt::{self, Debug};
3use std::marker::PhantomData;
4
5use solverforge_config::{
6    ConstructionHeuristicConfig, ConstructionHeuristicType, MoveSelectorConfig,
7};
8use solverforge_core::domain::{
9    SolutionDescriptor, UsizeEntityValueProvider, UsizeGetter, UsizeSetter, ValueRangeType,
10};
11use solverforge_scoring::Director;
12
13use crate::heuristic::r#move::Move;
14use crate::heuristic::selector::decorator::VecUnionSelector;
15use crate::heuristic::selector::move_selector::MoveSelector;
16use crate::heuristic::selector::EntityReference;
17use crate::phase::construction::{
18    BestFitForager, ConstructionHeuristicPhase, EntityPlacer, FirstFitForager, Placement,
19};
20use crate::scope::{ProgressCallback, SolverScope};
21
22#[derive(Clone)]
23struct VariableBinding {
24    descriptor_index: usize,
25    entity_type_name: &'static str,
26    variable_name: &'static str,
27    getter: UsizeGetter,
28    setter: UsizeSetter,
29    value_range_provider: Option<&'static str>,
30    provider: Option<UsizeEntityValueProvider>,
31    range_type: ValueRangeType,
32}
33
34impl Debug for VariableBinding {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        f.debug_struct("VariableBinding")
37            .field("descriptor_index", &self.descriptor_index)
38            .field("entity_type_name", &self.entity_type_name)
39            .field("variable_name", &self.variable_name)
40            .field("range_type", &self.range_type)
41            .finish()
42    }
43}
44
45impl VariableBinding {
46    fn values_for_entity(
47        &self,
48        solution_descriptor: &SolutionDescriptor,
49        solution: &dyn Any,
50        entity: &dyn Any,
51    ) -> Vec<usize> {
52        match (&self.provider, &self.range_type) {
53            (Some(provider), _) => provider(entity),
54            (_, ValueRangeType::CountableRange { from, to }) => {
55                let start = *from;
56                let end = *to;
57                (start..end)
58                    .filter_map(|value| usize::try_from(value).ok())
59                    .collect()
60            }
61            _ => self
62                .value_range_provider
63                .and_then(|provider_name| {
64                    solution_descriptor
65                        .problem_fact_descriptors
66                        .iter()
67                        .find(|descriptor| descriptor.solution_field == provider_name)
68                        .and_then(|descriptor| descriptor.extractor.as_ref())
69                        .and_then(|extractor| extractor.count(solution))
70                        .or_else(|| {
71                            solution_descriptor
72                                .entity_descriptors
73                                .iter()
74                                .find(|descriptor| descriptor.solution_field == provider_name)
75                                .and_then(|descriptor| descriptor.extractor.as_ref())
76                                .and_then(|extractor| extractor.count(solution))
77                        })
78                })
79                .map(|count| (0..count).collect())
80                .unwrap_or_default(),
81        }
82    }
83}
84
85#[derive(Clone)]
86pub struct DescriptorChangeMove<S> {
87    binding: VariableBinding,
88    entity_index: usize,
89    to_value: Option<usize>,
90    solution_descriptor: SolutionDescriptor,
91    _phantom: PhantomData<fn() -> S>,
92}
93
94impl<S> Debug for DescriptorChangeMove<S> {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        f.debug_struct("DescriptorChangeMove")
97            .field("descriptor_index", &self.binding.descriptor_index)
98            .field("entity_index", &self.entity_index)
99            .field("variable_name", &self.binding.variable_name)
100            .field("to_value", &self.to_value)
101            .finish()
102    }
103}
104
105impl<S: 'static> DescriptorChangeMove<S> {
106    fn new(
107        binding: VariableBinding,
108        entity_index: usize,
109        to_value: Option<usize>,
110        solution_descriptor: SolutionDescriptor,
111    ) -> Self {
112        Self {
113            binding,
114            entity_index,
115            to_value,
116            solution_descriptor,
117            _phantom: PhantomData,
118        }
119    }
120
121    fn current_value(&self, solution: &S) -> Option<usize> {
122        let entity = self
123            .solution_descriptor
124            .get_entity(
125                solution as &dyn Any,
126                self.binding.descriptor_index,
127                self.entity_index,
128            )
129            .expect("entity lookup failed for descriptor change move");
130        (self.binding.getter)(entity)
131    }
132}
133
134impl<S> Move<S> for DescriptorChangeMove<S>
135where
136    S: solverforge_core::domain::PlanningSolution + 'static,
137{
138    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
139        self.current_value(score_director.working_solution()) != self.to_value
140    }
141
142    fn do_move<D: Director<S>>(&self, score_director: &mut D) {
143        let old_value = self.current_value(score_director.working_solution());
144        score_director.before_variable_changed(self.binding.descriptor_index, self.entity_index);
145        let entity = self
146            .solution_descriptor
147            .get_entity_mut(
148                score_director.working_solution_mut() as &mut dyn Any,
149                self.binding.descriptor_index,
150                self.entity_index,
151            )
152            .expect("entity lookup failed for descriptor change move");
153        (self.binding.setter)(entity, self.to_value);
154        score_director.after_variable_changed(self.binding.descriptor_index, self.entity_index);
155
156        let descriptor = self.solution_descriptor.clone();
157        let binding = self.binding.clone();
158        let entity_index = self.entity_index;
159        score_director.register_undo(Box::new(move |solution: &mut S| {
160            let entity = descriptor
161                .get_entity_mut(
162                    solution as &mut dyn Any,
163                    binding.descriptor_index,
164                    entity_index,
165                )
166                .expect("entity lookup failed for descriptor change undo");
167            (binding.setter)(entity, old_value);
168        }));
169    }
170
171    fn descriptor_index(&self) -> usize {
172        self.binding.descriptor_index
173    }
174
175    fn entity_indices(&self) -> &[usize] {
176        std::slice::from_ref(&self.entity_index)
177    }
178
179    fn variable_name(&self) -> &str {
180        self.binding.variable_name
181    }
182}
183
184#[derive(Clone)]
185pub struct DescriptorSwapMove<S> {
186    binding: VariableBinding,
187    left_entity_index: usize,
188    right_entity_index: usize,
189    indices: [usize; 2],
190    solution_descriptor: SolutionDescriptor,
191    _phantom: PhantomData<fn() -> S>,
192}
193
194impl<S> Debug for DescriptorSwapMove<S> {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        f.debug_struct("DescriptorSwapMove")
197            .field("descriptor_index", &self.binding.descriptor_index)
198            .field("left_entity_index", &self.left_entity_index)
199            .field("right_entity_index", &self.right_entity_index)
200            .field("variable_name", &self.binding.variable_name)
201            .finish()
202    }
203}
204
205impl<S: 'static> DescriptorSwapMove<S> {
206    fn new(
207        binding: VariableBinding,
208        left_entity_index: usize,
209        right_entity_index: usize,
210        solution_descriptor: SolutionDescriptor,
211    ) -> Self {
212        Self {
213            binding,
214            left_entity_index,
215            right_entity_index,
216            indices: [left_entity_index, right_entity_index],
217            solution_descriptor,
218            _phantom: PhantomData,
219        }
220    }
221
222    fn current_value(&self, solution: &S, entity_index: usize) -> Option<usize> {
223        let entity = self
224            .solution_descriptor
225            .get_entity(
226                solution as &dyn Any,
227                self.binding.descriptor_index,
228                entity_index,
229            )
230            .expect("entity lookup failed for descriptor swap move");
231        (self.binding.getter)(entity)
232    }
233}
234
235impl<S> Move<S> for DescriptorSwapMove<S>
236where
237    S: solverforge_core::domain::PlanningSolution + 'static,
238{
239    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
240        self.left_entity_index != self.right_entity_index
241            && self.current_value(score_director.working_solution(), self.left_entity_index)
242                != self.current_value(score_director.working_solution(), self.right_entity_index)
243    }
244
245    fn do_move<D: Director<S>>(&self, score_director: &mut D) {
246        let left_value =
247            self.current_value(score_director.working_solution(), self.left_entity_index);
248        let right_value =
249            self.current_value(score_director.working_solution(), self.right_entity_index);
250
251        score_director
252            .before_variable_changed(self.binding.descriptor_index, self.left_entity_index);
253        score_director
254            .before_variable_changed(self.binding.descriptor_index, self.right_entity_index);
255
256        let left_entity = self
257            .solution_descriptor
258            .get_entity_mut(
259                score_director.working_solution_mut() as &mut dyn Any,
260                self.binding.descriptor_index,
261                self.left_entity_index,
262            )
263            .expect("entity lookup failed for descriptor swap move");
264        (self.binding.setter)(left_entity, right_value);
265
266        let right_entity = self
267            .solution_descriptor
268            .get_entity_mut(
269                score_director.working_solution_mut() as &mut dyn Any,
270                self.binding.descriptor_index,
271                self.right_entity_index,
272            )
273            .expect("entity lookup failed for descriptor swap move");
274        (self.binding.setter)(right_entity, left_value);
275
276        score_director
277            .after_variable_changed(self.binding.descriptor_index, self.left_entity_index);
278        score_director
279            .after_variable_changed(self.binding.descriptor_index, self.right_entity_index);
280
281        let descriptor = self.solution_descriptor.clone();
282        let binding = self.binding.clone();
283        let left_entity_index = self.left_entity_index;
284        let right_entity_index = self.right_entity_index;
285        score_director.register_undo(Box::new(move |solution: &mut S| {
286            let left_entity = descriptor
287                .get_entity_mut(
288                    solution as &mut dyn Any,
289                    binding.descriptor_index,
290                    left_entity_index,
291                )
292                .expect("entity lookup failed for descriptor swap undo");
293            (binding.setter)(left_entity, left_value);
294            let right_entity = descriptor
295                .get_entity_mut(
296                    solution as &mut dyn Any,
297                    binding.descriptor_index,
298                    right_entity_index,
299                )
300                .expect("entity lookup failed for descriptor swap undo");
301            (binding.setter)(right_entity, right_value);
302        }));
303    }
304
305    fn descriptor_index(&self) -> usize {
306        self.binding.descriptor_index
307    }
308
309    fn entity_indices(&self) -> &[usize] {
310        &self.indices
311    }
312
313    fn variable_name(&self) -> &str {
314        self.binding.variable_name
315    }
316}
317
318#[derive(Clone)]
319pub enum DescriptorEitherMove<S> {
320    Change(DescriptorChangeMove<S>),
321    Swap(DescriptorSwapMove<S>),
322}
323
324impl<S> Debug for DescriptorEitherMove<S> {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        match self {
327            Self::Change(m) => m.fmt(f),
328            Self::Swap(m) => m.fmt(f),
329        }
330    }
331}
332
333impl<S> Move<S> for DescriptorEitherMove<S>
334where
335    S: solverforge_core::domain::PlanningSolution + 'static,
336{
337    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
338        match self {
339            Self::Change(m) => m.is_doable(score_director),
340            Self::Swap(m) => m.is_doable(score_director),
341        }
342    }
343
344    fn do_move<D: Director<S>>(&self, score_director: &mut D) {
345        match self {
346            Self::Change(m) => m.do_move(score_director),
347            Self::Swap(m) => m.do_move(score_director),
348        }
349    }
350
351    fn descriptor_index(&self) -> usize {
352        match self {
353            Self::Change(m) => m.descriptor_index(),
354            Self::Swap(m) => m.descriptor_index(),
355        }
356    }
357
358    fn entity_indices(&self) -> &[usize] {
359        match self {
360            Self::Change(m) => m.entity_indices(),
361            Self::Swap(m) => m.entity_indices(),
362        }
363    }
364
365    fn variable_name(&self) -> &str {
366        match self {
367            Self::Change(m) => m.variable_name(),
368            Self::Swap(m) => m.variable_name(),
369        }
370    }
371}
372
373#[derive(Clone)]
374pub struct DescriptorChangeMoveSelector<S> {
375    binding: VariableBinding,
376    solution_descriptor: SolutionDescriptor,
377    _phantom: PhantomData<fn() -> S>,
378}
379
380impl<S> Debug for DescriptorChangeMoveSelector<S> {
381    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382        f.debug_struct("DescriptorChangeMoveSelector")
383            .field("binding", &self.binding)
384            .finish()
385    }
386}
387
388impl<S> DescriptorChangeMoveSelector<S> {
389    fn new(binding: VariableBinding, solution_descriptor: SolutionDescriptor) -> Self {
390        Self {
391            binding,
392            solution_descriptor,
393            _phantom: PhantomData,
394        }
395    }
396}
397
398impl<S> MoveSelector<S, DescriptorEitherMove<S>> for DescriptorChangeMoveSelector<S>
399where
400    S: solverforge_core::domain::PlanningSolution + 'static,
401{
402    fn iter_moves<'a, D: Director<S>>(
403        &'a self,
404        score_director: &'a D,
405    ) -> impl Iterator<Item = DescriptorEitherMove<S>> + 'a {
406        let count = score_director
407            .entity_count(self.binding.descriptor_index)
408            .unwrap_or(0);
409        let descriptor = self.solution_descriptor.clone();
410        let binding = self.binding.clone();
411        let solution = score_director.working_solution() as &dyn Any;
412        (0..count).flat_map(move |entity_index| {
413            let entity = descriptor
414                .get_entity(solution, binding.descriptor_index, entity_index)
415                .expect("entity lookup failed for change selector");
416            binding
417                .values_for_entity(&descriptor, solution, entity)
418                .into_iter()
419                .map({
420                    let binding = binding.clone();
421                    let descriptor = descriptor.clone();
422                    move |value| {
423                        DescriptorEitherMove::Change(DescriptorChangeMove::new(
424                            binding.clone(),
425                            entity_index,
426                            Some(value),
427                            descriptor.clone(),
428                        ))
429                    }
430                })
431        })
432    }
433
434    fn size<D: Director<S>>(&self, score_director: &D) -> usize {
435        let count = score_director
436            .entity_count(self.binding.descriptor_index)
437            .unwrap_or(0);
438        let mut total = 0;
439        for entity_index in 0..count {
440            let entity = self
441                .solution_descriptor
442                .get_entity(
443                    score_director.working_solution() as &dyn Any,
444                    self.binding.descriptor_index,
445                    entity_index,
446                )
447                .expect("entity lookup failed for change selector");
448            total += self
449                .binding
450                .values_for_entity(
451                    &self.solution_descriptor,
452                    score_director.working_solution() as &dyn Any,
453                    entity,
454                )
455                .len();
456        }
457        total
458    }
459}
460
461#[derive(Clone)]
462pub struct DescriptorSwapMoveSelector<S> {
463    binding: VariableBinding,
464    solution_descriptor: SolutionDescriptor,
465    _phantom: PhantomData<fn() -> S>,
466}
467
468impl<S> Debug for DescriptorSwapMoveSelector<S> {
469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470        f.debug_struct("DescriptorSwapMoveSelector")
471            .field("binding", &self.binding)
472            .finish()
473    }
474}
475
476impl<S> DescriptorSwapMoveSelector<S> {
477    fn new(binding: VariableBinding, solution_descriptor: SolutionDescriptor) -> Self {
478        Self {
479            binding,
480            solution_descriptor,
481            _phantom: PhantomData,
482        }
483    }
484}
485
486impl<S> MoveSelector<S, DescriptorEitherMove<S>> for DescriptorSwapMoveSelector<S>
487where
488    S: solverforge_core::domain::PlanningSolution + 'static,
489{
490    fn iter_moves<'a, D: Director<S>>(
491        &'a self,
492        score_director: &'a D,
493    ) -> impl Iterator<Item = DescriptorEitherMove<S>> + 'a {
494        let count = score_director
495            .entity_count(self.binding.descriptor_index)
496            .unwrap_or(0);
497        let binding = self.binding.clone();
498        let descriptor = self.solution_descriptor.clone();
499        (0..count).flat_map(move |left_entity_index| {
500            ((left_entity_index + 1)..count).map({
501                let binding = binding.clone();
502                let descriptor = descriptor.clone();
503                move |right_entity_index| {
504                    DescriptorEitherMove::Swap(DescriptorSwapMove::new(
505                        binding.clone(),
506                        left_entity_index,
507                        right_entity_index,
508                        descriptor.clone(),
509                    ))
510                }
511            })
512        })
513    }
514
515    fn size<D: Director<S>>(&self, score_director: &D) -> usize {
516        let count = score_director
517            .entity_count(self.binding.descriptor_index)
518            .unwrap_or(0);
519        count.saturating_mul(count.saturating_sub(1)) / 2
520    }
521}
522
523#[derive(Clone)]
524pub enum DescriptorLeafSelector<S> {
525    Change(DescriptorChangeMoveSelector<S>),
526    Swap(DescriptorSwapMoveSelector<S>),
527}
528
529impl<S> Debug for DescriptorLeafSelector<S>
530where
531    S: solverforge_core::domain::PlanningSolution,
532{
533    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
534        match self {
535            Self::Change(selector) => selector.fmt(f),
536            Self::Swap(selector) => selector.fmt(f),
537        }
538    }
539}
540
541impl<S> MoveSelector<S, DescriptorEitherMove<S>> for DescriptorLeafSelector<S>
542where
543    S: solverforge_core::domain::PlanningSolution + 'static,
544{
545    fn iter_moves<'a, D: Director<S>>(
546        &'a self,
547        score_director: &'a D,
548    ) -> impl Iterator<Item = DescriptorEitherMove<S>> + 'a {
549        enum DescriptorLeafIter<A, B> {
550            Change(A),
551            Swap(B),
552        }
553
554        impl<T, A, B> Iterator for DescriptorLeafIter<A, B>
555        where
556            A: Iterator<Item = T>,
557            B: Iterator<Item = T>,
558        {
559            type Item = T;
560
561            fn next(&mut self) -> Option<Self::Item> {
562                match self {
563                    Self::Change(iter) => iter.next(),
564                    Self::Swap(iter) => iter.next(),
565                }
566            }
567        }
568
569        match self {
570            Self::Change(selector) => {
571                DescriptorLeafIter::Change(selector.iter_moves(score_director))
572            }
573            Self::Swap(selector) => DescriptorLeafIter::Swap(selector.iter_moves(score_director)),
574        }
575    }
576
577    fn size<D: Director<S>>(&self, score_director: &D) -> usize {
578        match self {
579            Self::Change(selector) => selector.size(score_director),
580            Self::Swap(selector) => selector.size(score_director),
581        }
582    }
583}
584
585pub enum DescriptorConstruction<S: solverforge_core::domain::PlanningSolution> {
586    FirstFit(
587        ConstructionHeuristicPhase<
588            S,
589            DescriptorEitherMove<S>,
590            DescriptorEntityPlacer<S>,
591            FirstFitForager<S, DescriptorEitherMove<S>>,
592        >,
593    ),
594    BestFit(
595        ConstructionHeuristicPhase<
596            S,
597            DescriptorEitherMove<S>,
598            DescriptorEntityPlacer<S>,
599            BestFitForager<S, DescriptorEitherMove<S>>,
600        >,
601    ),
602}
603
604impl<S: solverforge_core::domain::PlanningSolution> Debug for DescriptorConstruction<S> {
605    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606        match self {
607            Self::FirstFit(phase) => write!(f, "DescriptorConstruction::FirstFit({phase:?})"),
608            Self::BestFit(phase) => write!(f, "DescriptorConstruction::BestFit({phase:?})"),
609        }
610    }
611}
612
613impl<S, D, ProgressCb> crate::phase::Phase<S, D, ProgressCb> for DescriptorConstruction<S>
614where
615    S: solverforge_core::domain::PlanningSolution + 'static,
616    D: Director<S>,
617    ProgressCb: ProgressCallback<S>,
618{
619    fn solve(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
620        match self {
621            Self::FirstFit(phase) => phase.solve(solver_scope),
622            Self::BestFit(phase) => phase.solve(solver_scope),
623        }
624    }
625
626    fn phase_type_name(&self) -> &'static str {
627        "DescriptorConstruction"
628    }
629}
630
631#[derive(Clone)]
632pub struct DescriptorEntityPlacer<S> {
633    bindings: Vec<VariableBinding>,
634    solution_descriptor: SolutionDescriptor,
635    _phantom: PhantomData<fn() -> S>,
636}
637
638impl<S> Debug for DescriptorEntityPlacer<S> {
639    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
640        f.debug_struct("DescriptorEntityPlacer")
641            .field("bindings", &self.bindings)
642            .finish()
643    }
644}
645
646impl<S> DescriptorEntityPlacer<S> {
647    fn new(bindings: Vec<VariableBinding>, solution_descriptor: SolutionDescriptor) -> Self {
648        Self {
649            bindings,
650            solution_descriptor,
651            _phantom: PhantomData,
652        }
653    }
654}
655
656impl<S> EntityPlacer<S, DescriptorEitherMove<S>> for DescriptorEntityPlacer<S>
657where
658    S: solverforge_core::domain::PlanningSolution + 'static,
659{
660    fn get_placements<D: Director<S>>(
661        &self,
662        score_director: &D,
663    ) -> Vec<Placement<S, DescriptorEitherMove<S>>> {
664        let mut placements = Vec::new();
665
666        for binding in &self.bindings {
667            let count = score_director
668                .entity_count(binding.descriptor_index)
669                .unwrap_or(0);
670
671            for entity_index in 0..count {
672                let entity = self
673                    .solution_descriptor
674                    .get_entity(
675                        score_director.working_solution() as &dyn Any,
676                        binding.descriptor_index,
677                        entity_index,
678                    )
679                    .expect("entity lookup failed for descriptor construction");
680                let current_value = (binding.getter)(entity);
681                if current_value.is_some() {
682                    continue;
683                }
684
685                let moves = binding
686                    .values_for_entity(
687                        &self.solution_descriptor,
688                        score_director.working_solution() as &dyn Any,
689                        entity,
690                    )
691                    .into_iter()
692                    .map(|value| {
693                        DescriptorEitherMove::Change(DescriptorChangeMove::new(
694                            binding.clone(),
695                            entity_index,
696                            Some(value),
697                            self.solution_descriptor.clone(),
698                        ))
699                    })
700                    .collect::<Vec<_>>();
701
702                if moves.is_empty() {
703                    continue;
704                }
705
706                placements.push(Placement::new(
707                    EntityReference::new(binding.descriptor_index, entity_index),
708                    moves,
709                ));
710            }
711        }
712
713        placements
714    }
715}
716
717fn collect_bindings(descriptor: &SolutionDescriptor) -> Vec<VariableBinding> {
718    let mut bindings = Vec::new();
719    for (descriptor_index, entity_descriptor) in descriptor.entity_descriptors.iter().enumerate() {
720        for variable in entity_descriptor.genuine_variable_descriptors() {
721            let Some(getter) = variable.usize_getter else {
722                continue;
723            };
724            let Some(setter) = variable.usize_setter else {
725                continue;
726            };
727            bindings.push(VariableBinding {
728                descriptor_index,
729                entity_type_name: entity_descriptor.type_name,
730                variable_name: variable.name,
731                getter,
732                setter,
733                value_range_provider: variable.value_range_provider,
734                provider: variable.entity_value_provider,
735                range_type: variable.value_range_type.clone(),
736            });
737        }
738    }
739    bindings
740}
741
742fn find_binding(
743    bindings: &[VariableBinding],
744    entity_class: Option<&str>,
745    variable_name: Option<&str>,
746) -> Vec<VariableBinding> {
747    bindings
748        .iter()
749        .filter(|binding| entity_class.is_none_or(|name| name == binding.entity_type_name))
750        .filter(|binding| variable_name.is_none_or(|name| name == binding.variable_name))
751        .cloned()
752        .collect()
753}
754
755pub fn descriptor_has_bindings(descriptor: &SolutionDescriptor) -> bool {
756    !collect_bindings(descriptor).is_empty()
757}
758
759pub fn standard_work_remaining<S>(
760    descriptor: &SolutionDescriptor,
761    entity_class: Option<&str>,
762    variable_name: Option<&str>,
763    solution: &S,
764) -> bool
765where
766    S: solverforge_core::domain::PlanningSolution + 'static,
767{
768    let bindings = find_binding(&collect_bindings(descriptor), entity_class, variable_name);
769    for binding in bindings {
770        let Some(entity_count) = descriptor
771            .entity_descriptors
772            .get(binding.descriptor_index)
773            .and_then(|entity| entity.entity_count(solution as &dyn Any))
774        else {
775            continue;
776        };
777        for entity_index in 0..entity_count {
778            let entity = descriptor
779                .get_entity(solution as &dyn Any, binding.descriptor_index, entity_index)
780                .expect("entity lookup failed while checking standard work");
781            if (binding.getter)(entity).is_none()
782                && !binding
783                    .values_for_entity(descriptor, solution as &dyn Any, entity)
784                    .is_empty()
785            {
786                return true;
787            }
788        }
789    }
790    false
791}
792
793pub fn standard_target_matches(
794    descriptor: &SolutionDescriptor,
795    entity_class: Option<&str>,
796    variable_name: Option<&str>,
797) -> bool {
798    !find_binding(&collect_bindings(descriptor), entity_class, variable_name).is_empty()
799}
800
801fn collect_descriptor_leaf_selectors<S>(
802    config: Option<&MoveSelectorConfig>,
803    descriptor: &SolutionDescriptor,
804) -> Vec<DescriptorLeafSelector<S>>
805where
806    S: solverforge_core::domain::PlanningSolution + 'static,
807{
808    let bindings = collect_bindings(descriptor);
809    let mut leaves = Vec::new();
810
811    fn collect<S>(
812        cfg: &MoveSelectorConfig,
813        descriptor: &SolutionDescriptor,
814        bindings: &[VariableBinding],
815        leaves: &mut Vec<DescriptorLeafSelector<S>>,
816    ) where
817        S: solverforge_core::domain::PlanningSolution + 'static,
818    {
819        match cfg {
820            MoveSelectorConfig::ChangeMoveSelector(change) => {
821                let matched = find_binding(
822                    bindings,
823                    change.target.entity_class.as_deref(),
824                    change.target.variable_name.as_deref(),
825                );
826                assert!(
827                    !matched.is_empty(),
828                    "change_move selector matched no standard planning variables for entity_class={:?} variable_name={:?}",
829                    change.target.entity_class,
830                    change.target.variable_name
831                );
832                for binding in matched {
833                    leaves.push(DescriptorLeafSelector::Change(
834                        DescriptorChangeMoveSelector::new(binding, descriptor.clone()),
835                    ));
836                }
837            }
838            MoveSelectorConfig::SwapMoveSelector(swap) => {
839                let matched = find_binding(
840                    bindings,
841                    swap.target.entity_class.as_deref(),
842                    swap.target.variable_name.as_deref(),
843                );
844                assert!(
845                    !matched.is_empty(),
846                    "swap_move selector matched no standard planning variables for entity_class={:?} variable_name={:?}",
847                    swap.target.entity_class,
848                    swap.target.variable_name
849                );
850                for binding in matched {
851                    leaves.push(DescriptorLeafSelector::Swap(
852                        DescriptorSwapMoveSelector::new(binding, descriptor.clone()),
853                    ));
854                }
855            }
856            MoveSelectorConfig::UnionMoveSelector(union) => {
857                for child in &union.selectors {
858                    collect(child, descriptor, bindings, leaves);
859                }
860            }
861            MoveSelectorConfig::SelectedCountLimitMoveSelector(_) => {
862                panic!(
863                    "selected_count_limit_move_selector must be handled by the unified stock runtime"
864                );
865            }
866            MoveSelectorConfig::ListChangeMoveSelector(_)
867            | MoveSelectorConfig::NearbyListChangeMoveSelector(_)
868            | MoveSelectorConfig::ListSwapMoveSelector(_)
869            | MoveSelectorConfig::NearbyListSwapMoveSelector(_)
870            | MoveSelectorConfig::SubListChangeMoveSelector(_)
871            | MoveSelectorConfig::SubListSwapMoveSelector(_)
872            | MoveSelectorConfig::ListReverseMoveSelector(_)
873            | MoveSelectorConfig::KOptMoveSelector(_)
874            | MoveSelectorConfig::ListRuinMoveSelector(_) => {
875                panic!("list move selector configured against a standard-variable stock context");
876            }
877            MoveSelectorConfig::CartesianProductMoveSelector(_) => {
878                panic!("cartesian_product move selectors are not supported in stock solving");
879            }
880        }
881    }
882
883    match config {
884        Some(cfg) => collect(cfg, descriptor, &bindings, &mut leaves),
885        None => {
886            for binding in bindings {
887                leaves.push(DescriptorLeafSelector::Change(
888                    DescriptorChangeMoveSelector::new(binding.clone(), descriptor.clone()),
889                ));
890                leaves.push(DescriptorLeafSelector::Swap(
891                    DescriptorSwapMoveSelector::new(binding, descriptor.clone()),
892                ));
893            }
894        }
895    }
896
897    assert!(
898        !leaves.is_empty(),
899        "stock move selector configuration produced no standard neighborhoods"
900    );
901
902    leaves
903}
904
905pub fn build_descriptor_move_selector<S>(
906    config: Option<&MoveSelectorConfig>,
907    descriptor: &SolutionDescriptor,
908) -> VecUnionSelector<S, DescriptorEitherMove<S>, DescriptorLeafSelector<S>>
909where
910    S: solverforge_core::domain::PlanningSolution + 'static,
911{
912    VecUnionSelector::new(collect_descriptor_leaf_selectors(config, descriptor))
913}
914
915pub fn build_descriptor_construction<S>(
916    config: Option<&ConstructionHeuristicConfig>,
917    descriptor: &SolutionDescriptor,
918) -> DescriptorConstruction<S>
919where
920    S: solverforge_core::domain::PlanningSolution + 'static,
921{
922    let bindings = config
923        .map(|cfg| {
924            let matched = find_binding(
925                &collect_bindings(descriptor),
926                cfg.target.entity_class.as_deref(),
927                cfg.target.variable_name.as_deref(),
928            );
929            assert!(
930                !matched.is_empty(),
931                "construction heuristic matched no standard planning variables for entity_class={:?} variable_name={:?}",
932                cfg.target.entity_class,
933                cfg.target.variable_name
934            );
935            matched
936        })
937        .unwrap_or_else(|| collect_bindings(descriptor));
938    let placer = DescriptorEntityPlacer::new(bindings, descriptor.clone());
939    let construction_type = config
940        .map(|cfg| cfg.construction_heuristic_type)
941        .unwrap_or(ConstructionHeuristicType::FirstFit);
942
943    match construction_type {
944        ConstructionHeuristicType::FirstFit => DescriptorConstruction::FirstFit(
945            ConstructionHeuristicPhase::new(placer, FirstFitForager::new()),
946        ),
947        ConstructionHeuristicType::CheapestInsertion => DescriptorConstruction::BestFit(
948            ConstructionHeuristicPhase::new(placer, BestFitForager::new()),
949        ),
950        ConstructionHeuristicType::FirstFitDecreasing
951        | ConstructionHeuristicType::WeakestFit
952        | ConstructionHeuristicType::WeakestFitDecreasing
953        | ConstructionHeuristicType::StrongestFit
954        | ConstructionHeuristicType::StrongestFitDecreasing
955        | ConstructionHeuristicType::AllocateEntityFromQueue
956        | ConstructionHeuristicType::AllocateToValueFromQueue
957        | ConstructionHeuristicType::ListRoundRobin
958        | ConstructionHeuristicType::ListCheapestInsertion
959        | ConstructionHeuristicType::ListRegretInsertion
960        | ConstructionHeuristicType::ListClarkeWright
961        | ConstructionHeuristicType::ListKOpt => {
962            panic!(
963                "descriptor standard construction does not support {:?}",
964                construction_type
965            );
966        }
967    }
968}
969
970#[cfg(test)]
971mod tests {
972    use super::*;
973    use solverforge_core::domain::{
974        EntityCollectionExtractor, EntityDescriptor, PlanningSolution, ProblemFactDescriptor,
975        VariableDescriptor,
976    };
977    use solverforge_core::score::SoftScore;
978    use solverforge_scoring::ScoreDirector;
979    use std::any::{Any, TypeId};
980
981    #[derive(Clone, Debug)]
982    struct Worker;
983
984    #[derive(Clone, Debug)]
985    struct Task {
986        worker_idx: Option<usize>,
987    }
988
989    #[derive(Clone, Debug)]
990    struct Plan {
991        workers: Vec<Worker>,
992        tasks: Vec<Task>,
993        score: Option<SoftScore>,
994    }
995
996    impl PlanningSolution for Plan {
997        type Score = SoftScore;
998
999        fn score(&self) -> Option<Self::Score> {
1000            self.score
1001        }
1002
1003        fn set_score(&mut self, score: Option<Self::Score>) {
1004            self.score = score;
1005        }
1006    }
1007
1008    fn get_worker_idx(entity: &dyn Any) -> Option<usize> {
1009        entity
1010            .downcast_ref::<Task>()
1011            .expect("task expected")
1012            .worker_idx
1013    }
1014
1015    fn set_worker_idx(entity: &mut dyn Any, value: Option<usize>) {
1016        entity
1017            .downcast_mut::<Task>()
1018            .expect("task expected")
1019            .worker_idx = value;
1020    }
1021
1022    fn descriptor() -> SolutionDescriptor {
1023        SolutionDescriptor::new("Plan", TypeId::of::<Plan>())
1024            .with_entity(
1025                EntityDescriptor::new("Task", TypeId::of::<Task>(), "tasks")
1026                    .with_extractor(Box::new(EntityCollectionExtractor::new(
1027                        "Task",
1028                        "tasks",
1029                        |s: &Plan| &s.tasks,
1030                        |s: &mut Plan| &mut s.tasks,
1031                    )))
1032                    .with_variable(
1033                        VariableDescriptor::genuine("worker_idx")
1034                            .with_allows_unassigned(true)
1035                            .with_value_range("workers")
1036                            .with_usize_accessors(get_worker_idx, set_worker_idx),
1037                    ),
1038            )
1039            .with_problem_fact(
1040                ProblemFactDescriptor::new("Worker", TypeId::of::<Worker>(), "workers")
1041                    .with_extractor(Box::new(EntityCollectionExtractor::new(
1042                        "Worker",
1043                        "workers",
1044                        |s: &Plan| &s.workers,
1045                        |s: &mut Plan| &mut s.workers,
1046                    ))),
1047            )
1048    }
1049
1050    #[test]
1051    fn solution_level_value_range_generates_standard_work() {
1052        let descriptor = descriptor();
1053        let plan = Plan {
1054            workers: vec![Worker, Worker, Worker],
1055            tasks: vec![Task { worker_idx: None }],
1056            score: None,
1057        };
1058
1059        assert!(standard_work_remaining(
1060            &descriptor,
1061            Some("Task"),
1062            Some("worker_idx"),
1063            &plan
1064        ));
1065    }
1066
1067    #[test]
1068    fn solution_level_value_range_builds_change_moves() {
1069        let descriptor = descriptor();
1070        let plan = Plan {
1071            workers: vec![Worker, Worker, Worker],
1072            tasks: vec![Task { worker_idx: None }],
1073            score: None,
1074        };
1075        let director = ScoreDirector::simple(plan, descriptor.clone(), |s, _| s.tasks.len());
1076        let selector = build_descriptor_move_selector::<Plan>(None, &descriptor);
1077
1078        // One change neighborhood contributes 3 moves; the swap neighborhood contributes 0.
1079        assert_eq!(selector.size(&director), 3);
1080    }
1081}