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 provider: Option<UsizeEntityValueProvider>,
30 range_type: ValueRangeType,
31}
32
33impl Debug for VariableBinding {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.debug_struct("VariableBinding")
36 .field("descriptor_index", &self.descriptor_index)
37 .field("entity_type_name", &self.entity_type_name)
38 .field("variable_name", &self.variable_name)
39 .field("range_type", &self.range_type)
40 .finish()
41 }
42}
43
44impl VariableBinding {
45 fn values_for_entity(&self, entity: &dyn Any) -> Vec<usize> {
46 match (&self.provider, &self.range_type) {
47 (Some(provider), _) => provider(entity),
48 (_, ValueRangeType::CountableRange { from, to }) => {
49 let start = *from;
50 let end = *to;
51 (start..end)
52 .filter_map(|value| usize::try_from(value).ok())
53 .collect()
54 }
55 _ => Vec::new(),
56 }
57 }
58}
59
60#[derive(Clone)]
61pub struct DescriptorChangeMove<S> {
62 binding: VariableBinding,
63 entity_index: usize,
64 to_value: Option<usize>,
65 solution_descriptor: SolutionDescriptor,
66 _phantom: PhantomData<fn() -> S>,
67}
68
69impl<S> Debug for DescriptorChangeMove<S> {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 f.debug_struct("DescriptorChangeMove")
72 .field("descriptor_index", &self.binding.descriptor_index)
73 .field("entity_index", &self.entity_index)
74 .field("variable_name", &self.binding.variable_name)
75 .field("to_value", &self.to_value)
76 .finish()
77 }
78}
79
80impl<S: 'static> DescriptorChangeMove<S> {
81 fn new(
82 binding: VariableBinding,
83 entity_index: usize,
84 to_value: Option<usize>,
85 solution_descriptor: SolutionDescriptor,
86 ) -> Self {
87 Self {
88 binding,
89 entity_index,
90 to_value,
91 solution_descriptor,
92 _phantom: PhantomData,
93 }
94 }
95
96 fn current_value(&self, solution: &S) -> Option<usize> {
97 let entity = self
98 .solution_descriptor
99 .get_entity(
100 solution as &dyn Any,
101 self.binding.descriptor_index,
102 self.entity_index,
103 )
104 .expect("entity lookup failed for descriptor change move");
105 (self.binding.getter)(entity)
106 }
107}
108
109impl<S> Move<S> for DescriptorChangeMove<S>
110where
111 S: solverforge_core::domain::PlanningSolution + 'static,
112{
113 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
114 self.current_value(score_director.working_solution()) != self.to_value
115 }
116
117 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
118 let old_value = self.current_value(score_director.working_solution());
119 score_director.before_variable_changed(self.binding.descriptor_index, self.entity_index);
120 let entity = self
121 .solution_descriptor
122 .get_entity_mut(
123 score_director.working_solution_mut() as &mut dyn Any,
124 self.binding.descriptor_index,
125 self.entity_index,
126 )
127 .expect("entity lookup failed for descriptor change move");
128 (self.binding.setter)(entity, self.to_value);
129 score_director.after_variable_changed(self.binding.descriptor_index, self.entity_index);
130
131 let descriptor = self.solution_descriptor.clone();
132 let binding = self.binding.clone();
133 let entity_index = self.entity_index;
134 score_director.register_undo(Box::new(move |solution: &mut S| {
135 let entity = descriptor
136 .get_entity_mut(
137 solution as &mut dyn Any,
138 binding.descriptor_index,
139 entity_index,
140 )
141 .expect("entity lookup failed for descriptor change undo");
142 (binding.setter)(entity, old_value);
143 }));
144 }
145
146 fn descriptor_index(&self) -> usize {
147 self.binding.descriptor_index
148 }
149
150 fn entity_indices(&self) -> &[usize] {
151 std::slice::from_ref(&self.entity_index)
152 }
153
154 fn variable_name(&self) -> &str {
155 self.binding.variable_name
156 }
157}
158
159#[derive(Clone)]
160pub struct DescriptorSwapMove<S> {
161 binding: VariableBinding,
162 left_entity_index: usize,
163 right_entity_index: usize,
164 indices: [usize; 2],
165 solution_descriptor: SolutionDescriptor,
166 _phantom: PhantomData<fn() -> S>,
167}
168
169impl<S> Debug for DescriptorSwapMove<S> {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 f.debug_struct("DescriptorSwapMove")
172 .field("descriptor_index", &self.binding.descriptor_index)
173 .field("left_entity_index", &self.left_entity_index)
174 .field("right_entity_index", &self.right_entity_index)
175 .field("variable_name", &self.binding.variable_name)
176 .finish()
177 }
178}
179
180impl<S: 'static> DescriptorSwapMove<S> {
181 fn new(
182 binding: VariableBinding,
183 left_entity_index: usize,
184 right_entity_index: usize,
185 solution_descriptor: SolutionDescriptor,
186 ) -> Self {
187 Self {
188 binding,
189 left_entity_index,
190 right_entity_index,
191 indices: [left_entity_index, right_entity_index],
192 solution_descriptor,
193 _phantom: PhantomData,
194 }
195 }
196
197 fn current_value(&self, solution: &S, entity_index: usize) -> Option<usize> {
198 let entity = self
199 .solution_descriptor
200 .get_entity(
201 solution as &dyn Any,
202 self.binding.descriptor_index,
203 entity_index,
204 )
205 .expect("entity lookup failed for descriptor swap move");
206 (self.binding.getter)(entity)
207 }
208}
209
210impl<S> Move<S> for DescriptorSwapMove<S>
211where
212 S: solverforge_core::domain::PlanningSolution + 'static,
213{
214 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
215 self.left_entity_index != self.right_entity_index
216 && self.current_value(score_director.working_solution(), self.left_entity_index)
217 != self.current_value(score_director.working_solution(), self.right_entity_index)
218 }
219
220 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
221 let left_value =
222 self.current_value(score_director.working_solution(), self.left_entity_index);
223 let right_value =
224 self.current_value(score_director.working_solution(), self.right_entity_index);
225
226 score_director
227 .before_variable_changed(self.binding.descriptor_index, self.left_entity_index);
228 score_director
229 .before_variable_changed(self.binding.descriptor_index, self.right_entity_index);
230
231 let left_entity = self
232 .solution_descriptor
233 .get_entity_mut(
234 score_director.working_solution_mut() as &mut dyn Any,
235 self.binding.descriptor_index,
236 self.left_entity_index,
237 )
238 .expect("entity lookup failed for descriptor swap move");
239 (self.binding.setter)(left_entity, right_value);
240
241 let right_entity = self
242 .solution_descriptor
243 .get_entity_mut(
244 score_director.working_solution_mut() as &mut dyn Any,
245 self.binding.descriptor_index,
246 self.right_entity_index,
247 )
248 .expect("entity lookup failed for descriptor swap move");
249 (self.binding.setter)(right_entity, left_value);
250
251 score_director
252 .after_variable_changed(self.binding.descriptor_index, self.left_entity_index);
253 score_director
254 .after_variable_changed(self.binding.descriptor_index, self.right_entity_index);
255
256 let descriptor = self.solution_descriptor.clone();
257 let binding = self.binding.clone();
258 let left_entity_index = self.left_entity_index;
259 let right_entity_index = self.right_entity_index;
260 score_director.register_undo(Box::new(move |solution: &mut S| {
261 let left_entity = descriptor
262 .get_entity_mut(
263 solution as &mut dyn Any,
264 binding.descriptor_index,
265 left_entity_index,
266 )
267 .expect("entity lookup failed for descriptor swap undo");
268 (binding.setter)(left_entity, left_value);
269 let right_entity = descriptor
270 .get_entity_mut(
271 solution as &mut dyn Any,
272 binding.descriptor_index,
273 right_entity_index,
274 )
275 .expect("entity lookup failed for descriptor swap undo");
276 (binding.setter)(right_entity, right_value);
277 }));
278 }
279
280 fn descriptor_index(&self) -> usize {
281 self.binding.descriptor_index
282 }
283
284 fn entity_indices(&self) -> &[usize] {
285 &self.indices
286 }
287
288 fn variable_name(&self) -> &str {
289 self.binding.variable_name
290 }
291}
292
293#[derive(Clone)]
294pub enum DescriptorEitherMove<S> {
295 Change(DescriptorChangeMove<S>),
296 Swap(DescriptorSwapMove<S>),
297}
298
299impl<S> Debug for DescriptorEitherMove<S> {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 match self {
302 Self::Change(m) => m.fmt(f),
303 Self::Swap(m) => m.fmt(f),
304 }
305 }
306}
307
308impl<S> Move<S> for DescriptorEitherMove<S>
309where
310 S: solverforge_core::domain::PlanningSolution + 'static,
311{
312 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
313 match self {
314 Self::Change(m) => m.is_doable(score_director),
315 Self::Swap(m) => m.is_doable(score_director),
316 }
317 }
318
319 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
320 match self {
321 Self::Change(m) => m.do_move(score_director),
322 Self::Swap(m) => m.do_move(score_director),
323 }
324 }
325
326 fn descriptor_index(&self) -> usize {
327 match self {
328 Self::Change(m) => m.descriptor_index(),
329 Self::Swap(m) => m.descriptor_index(),
330 }
331 }
332
333 fn entity_indices(&self) -> &[usize] {
334 match self {
335 Self::Change(m) => m.entity_indices(),
336 Self::Swap(m) => m.entity_indices(),
337 }
338 }
339
340 fn variable_name(&self) -> &str {
341 match self {
342 Self::Change(m) => m.variable_name(),
343 Self::Swap(m) => m.variable_name(),
344 }
345 }
346}
347
348#[derive(Clone)]
349pub struct DescriptorChangeMoveSelector<S> {
350 binding: VariableBinding,
351 solution_descriptor: SolutionDescriptor,
352 _phantom: PhantomData<fn() -> S>,
353}
354
355impl<S> Debug for DescriptorChangeMoveSelector<S> {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 f.debug_struct("DescriptorChangeMoveSelector")
358 .field("binding", &self.binding)
359 .finish()
360 }
361}
362
363impl<S> DescriptorChangeMoveSelector<S> {
364 fn new(binding: VariableBinding, solution_descriptor: SolutionDescriptor) -> Self {
365 Self {
366 binding,
367 solution_descriptor,
368 _phantom: PhantomData,
369 }
370 }
371}
372
373impl<S> MoveSelector<S, DescriptorEitherMove<S>> for DescriptorChangeMoveSelector<S>
374where
375 S: solverforge_core::domain::PlanningSolution + 'static,
376{
377 fn iter_moves<'a, D: Director<S>>(
378 &'a self,
379 score_director: &'a D,
380 ) -> impl Iterator<Item = DescriptorEitherMove<S>> + 'a {
381 let count = score_director
382 .entity_count(self.binding.descriptor_index)
383 .unwrap_or(0);
384 let mut moves = Vec::new();
385 for entity_index in 0..count {
386 let entity = self
387 .solution_descriptor
388 .get_entity(
389 score_director.working_solution() as &dyn Any,
390 self.binding.descriptor_index,
391 entity_index,
392 )
393 .expect("entity lookup failed for change selector");
394 for value in self.binding.values_for_entity(entity) {
395 moves.push(DescriptorEitherMove::Change(DescriptorChangeMove::new(
396 self.binding.clone(),
397 entity_index,
398 Some(value),
399 self.solution_descriptor.clone(),
400 )));
401 }
402 }
403 moves.into_iter()
404 }
405
406 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
407 let count = score_director
408 .entity_count(self.binding.descriptor_index)
409 .unwrap_or(0);
410 let mut total = 0;
411 for entity_index in 0..count {
412 let entity = self
413 .solution_descriptor
414 .get_entity(
415 score_director.working_solution() as &dyn Any,
416 self.binding.descriptor_index,
417 entity_index,
418 )
419 .expect("entity lookup failed for change selector");
420 total += self.binding.values_for_entity(entity).len();
421 }
422 total
423 }
424}
425
426#[derive(Clone)]
427pub struct DescriptorSwapMoveSelector<S> {
428 binding: VariableBinding,
429 solution_descriptor: SolutionDescriptor,
430 _phantom: PhantomData<fn() -> S>,
431}
432
433impl<S> Debug for DescriptorSwapMoveSelector<S> {
434 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435 f.debug_struct("DescriptorSwapMoveSelector")
436 .field("binding", &self.binding)
437 .finish()
438 }
439}
440
441impl<S> DescriptorSwapMoveSelector<S> {
442 fn new(binding: VariableBinding, solution_descriptor: SolutionDescriptor) -> Self {
443 Self {
444 binding,
445 solution_descriptor,
446 _phantom: PhantomData,
447 }
448 }
449}
450
451impl<S> MoveSelector<S, DescriptorEitherMove<S>> for DescriptorSwapMoveSelector<S>
452where
453 S: solverforge_core::domain::PlanningSolution + 'static,
454{
455 fn iter_moves<'a, D: Director<S>>(
456 &'a self,
457 score_director: &'a D,
458 ) -> impl Iterator<Item = DescriptorEitherMove<S>> + 'a {
459 let count = score_director
460 .entity_count(self.binding.descriptor_index)
461 .unwrap_or(0);
462 let mut moves = Vec::new();
463 for left_entity_index in 0..count {
464 for right_entity_index in (left_entity_index + 1)..count {
465 moves.push(DescriptorEitherMove::Swap(DescriptorSwapMove::new(
466 self.binding.clone(),
467 left_entity_index,
468 right_entity_index,
469 self.solution_descriptor.clone(),
470 )));
471 }
472 }
473 moves.into_iter()
474 }
475
476 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
477 let count = score_director
478 .entity_count(self.binding.descriptor_index)
479 .unwrap_or(0);
480 count.saturating_mul(count.saturating_sub(1)) / 2
481 }
482}
483
484#[derive(Clone)]
485pub enum DescriptorLeafSelector<S> {
486 Change(DescriptorChangeMoveSelector<S>),
487 Swap(DescriptorSwapMoveSelector<S>),
488}
489
490impl<S> Debug for DescriptorLeafSelector<S>
491where
492 S: solverforge_core::domain::PlanningSolution,
493{
494 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495 match self {
496 Self::Change(selector) => selector.fmt(f),
497 Self::Swap(selector) => selector.fmt(f),
498 }
499 }
500}
501
502impl<S> MoveSelector<S, DescriptorEitherMove<S>> for DescriptorLeafSelector<S>
503where
504 S: solverforge_core::domain::PlanningSolution + 'static,
505{
506 fn iter_moves<'a, D: Director<S>>(
507 &'a self,
508 score_director: &'a D,
509 ) -> impl Iterator<Item = DescriptorEitherMove<S>> + 'a {
510 let moves: Vec<_> = match self {
511 Self::Change(selector) => selector.iter_moves(score_director).collect(),
512 Self::Swap(selector) => selector.iter_moves(score_director).collect(),
513 };
514 moves.into_iter()
515 }
516
517 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
518 match self {
519 Self::Change(selector) => selector.size(score_director),
520 Self::Swap(selector) => selector.size(score_director),
521 }
522 }
523}
524
525pub enum DescriptorConstruction<S: solverforge_core::domain::PlanningSolution> {
526 FirstFit(
527 ConstructionHeuristicPhase<
528 S,
529 DescriptorEitherMove<S>,
530 DescriptorEntityPlacer<S>,
531 FirstFitForager<S, DescriptorEitherMove<S>>,
532 >,
533 ),
534 BestFit(
535 ConstructionHeuristicPhase<
536 S,
537 DescriptorEitherMove<S>,
538 DescriptorEntityPlacer<S>,
539 BestFitForager<S, DescriptorEitherMove<S>>,
540 >,
541 ),
542}
543
544impl<S: solverforge_core::domain::PlanningSolution> Debug for DescriptorConstruction<S> {
545 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
546 match self {
547 Self::FirstFit(phase) => write!(f, "DescriptorConstruction::FirstFit({phase:?})"),
548 Self::BestFit(phase) => write!(f, "DescriptorConstruction::BestFit({phase:?})"),
549 }
550 }
551}
552
553impl<S, D, ProgressCb> crate::phase::Phase<S, D, ProgressCb> for DescriptorConstruction<S>
554where
555 S: solverforge_core::domain::PlanningSolution + 'static,
556 D: Director<S>,
557 ProgressCb: ProgressCallback<S>,
558{
559 fn solve(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
560 match self {
561 Self::FirstFit(phase) => phase.solve(solver_scope),
562 Self::BestFit(phase) => phase.solve(solver_scope),
563 }
564 }
565
566 fn phase_type_name(&self) -> &'static str {
567 "DescriptorConstruction"
568 }
569}
570
571#[derive(Clone)]
572pub struct DescriptorEntityPlacer<S> {
573 bindings: Vec<VariableBinding>,
574 solution_descriptor: SolutionDescriptor,
575 _phantom: PhantomData<fn() -> S>,
576}
577
578impl<S> Debug for DescriptorEntityPlacer<S> {
579 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580 f.debug_struct("DescriptorEntityPlacer")
581 .field("bindings", &self.bindings)
582 .finish()
583 }
584}
585
586impl<S> DescriptorEntityPlacer<S> {
587 fn new(bindings: Vec<VariableBinding>, solution_descriptor: SolutionDescriptor) -> Self {
588 Self {
589 bindings,
590 solution_descriptor,
591 _phantom: PhantomData,
592 }
593 }
594}
595
596impl<S> EntityPlacer<S, DescriptorEitherMove<S>> for DescriptorEntityPlacer<S>
597where
598 S: solverforge_core::domain::PlanningSolution + 'static,
599{
600 fn get_placements<D: Director<S>>(
601 &self,
602 score_director: &D,
603 ) -> Vec<Placement<S, DescriptorEitherMove<S>>> {
604 let mut placements = Vec::new();
605
606 for binding in &self.bindings {
607 let count = score_director
608 .entity_count(binding.descriptor_index)
609 .unwrap_or(0);
610
611 for entity_index in 0..count {
612 let entity = self
613 .solution_descriptor
614 .get_entity(
615 score_director.working_solution() as &dyn Any,
616 binding.descriptor_index,
617 entity_index,
618 )
619 .expect("entity lookup failed for descriptor construction");
620 let current_value = (binding.getter)(entity);
621 if current_value.is_some() {
622 continue;
623 }
624
625 let moves = binding
626 .values_for_entity(entity)
627 .into_iter()
628 .map(|value| {
629 DescriptorEitherMove::Change(DescriptorChangeMove::new(
630 binding.clone(),
631 entity_index,
632 Some(value),
633 self.solution_descriptor.clone(),
634 ))
635 })
636 .collect::<Vec<_>>();
637
638 if moves.is_empty() {
639 continue;
640 }
641
642 placements.push(Placement::new(
643 EntityReference::new(binding.descriptor_index, entity_index),
644 moves,
645 ));
646 }
647 }
648
649 placements
650 }
651}
652
653fn collect_bindings(descriptor: &SolutionDescriptor) -> Vec<VariableBinding> {
654 let mut bindings = Vec::new();
655 for (descriptor_index, entity_descriptor) in descriptor.entity_descriptors.iter().enumerate() {
656 for variable in entity_descriptor.genuine_variable_descriptors() {
657 let Some(getter) = variable.usize_getter else {
658 continue;
659 };
660 let Some(setter) = variable.usize_setter else {
661 continue;
662 };
663 bindings.push(VariableBinding {
664 descriptor_index,
665 entity_type_name: entity_descriptor.type_name,
666 variable_name: variable.name,
667 getter,
668 setter,
669 provider: variable.entity_value_provider,
670 range_type: variable.value_range_type.clone(),
671 });
672 }
673 }
674 bindings
675}
676
677fn find_binding(
678 bindings: &[VariableBinding],
679 entity_class: Option<&str>,
680 variable_name: Option<&str>,
681) -> Vec<VariableBinding> {
682 bindings
683 .iter()
684 .filter(|binding| entity_class.is_none_or(|name| name == binding.entity_type_name))
685 .filter(|binding| variable_name.is_none_or(|name| name == binding.variable_name))
686 .cloned()
687 .collect()
688}
689
690pub fn descriptor_has_bindings(descriptor: &SolutionDescriptor) -> bool {
691 !collect_bindings(descriptor).is_empty()
692}
693
694pub fn standard_work_remaining<S>(
695 descriptor: &SolutionDescriptor,
696 entity_class: Option<&str>,
697 variable_name: Option<&str>,
698 solution: &S,
699) -> bool
700where
701 S: solverforge_core::domain::PlanningSolution + 'static,
702{
703 let bindings = find_binding(&collect_bindings(descriptor), entity_class, variable_name);
704 for binding in bindings {
705 let Some(entity_count) = descriptor
706 .entity_descriptors
707 .get(binding.descriptor_index)
708 .and_then(|entity| entity.entity_count(solution as &dyn Any))
709 else {
710 continue;
711 };
712 for entity_index in 0..entity_count {
713 let entity = descriptor
714 .get_entity(solution as &dyn Any, binding.descriptor_index, entity_index)
715 .expect("entity lookup failed while checking standard work");
716 if (binding.getter)(entity).is_none() && !binding.values_for_entity(entity).is_empty() {
717 return true;
718 }
719 }
720 }
721 false
722}
723
724pub fn standard_target_matches(
725 descriptor: &SolutionDescriptor,
726 entity_class: Option<&str>,
727 variable_name: Option<&str>,
728) -> bool {
729 !find_binding(&collect_bindings(descriptor), entity_class, variable_name).is_empty()
730}
731
732fn collect_descriptor_leaf_selectors<S>(
733 config: Option<&MoveSelectorConfig>,
734 descriptor: &SolutionDescriptor,
735) -> Vec<DescriptorLeafSelector<S>>
736where
737 S: solverforge_core::domain::PlanningSolution + 'static,
738{
739 let bindings = collect_bindings(descriptor);
740 let mut leaves = Vec::new();
741
742 fn collect<S>(
743 cfg: &MoveSelectorConfig,
744 descriptor: &SolutionDescriptor,
745 bindings: &[VariableBinding],
746 leaves: &mut Vec<DescriptorLeafSelector<S>>,
747 ) where
748 S: solverforge_core::domain::PlanningSolution + 'static,
749 {
750 match cfg {
751 MoveSelectorConfig::ChangeMoveSelector(change) => {
752 let matched = find_binding(
753 bindings,
754 change.target.entity_class.as_deref(),
755 change.target.variable_name.as_deref(),
756 );
757 assert!(
758 !matched.is_empty(),
759 "change_move selector matched no standard planning variables for entity_class={:?} variable_name={:?}",
760 change.target.entity_class,
761 change.target.variable_name
762 );
763 for binding in matched {
764 leaves.push(DescriptorLeafSelector::Change(
765 DescriptorChangeMoveSelector::new(binding, descriptor.clone()),
766 ));
767 }
768 }
769 MoveSelectorConfig::SwapMoveSelector(swap) => {
770 let matched = find_binding(
771 bindings,
772 swap.target.entity_class.as_deref(),
773 swap.target.variable_name.as_deref(),
774 );
775 assert!(
776 !matched.is_empty(),
777 "swap_move selector matched no standard planning variables for entity_class={:?} variable_name={:?}",
778 swap.target.entity_class,
779 swap.target.variable_name
780 );
781 for binding in matched {
782 leaves.push(DescriptorLeafSelector::Swap(
783 DescriptorSwapMoveSelector::new(binding, descriptor.clone()),
784 ));
785 }
786 }
787 MoveSelectorConfig::UnionMoveSelector(union) => {
788 for child in &union.selectors {
789 collect(child, descriptor, bindings, leaves);
790 }
791 }
792 MoveSelectorConfig::ListChangeMoveSelector(_)
793 | MoveSelectorConfig::NearbyListChangeMoveSelector(_)
794 | MoveSelectorConfig::ListSwapMoveSelector(_)
795 | MoveSelectorConfig::NearbyListSwapMoveSelector(_)
796 | MoveSelectorConfig::SubListChangeMoveSelector(_)
797 | MoveSelectorConfig::SubListSwapMoveSelector(_)
798 | MoveSelectorConfig::ListReverseMoveSelector(_)
799 | MoveSelectorConfig::KOptMoveSelector(_)
800 | MoveSelectorConfig::ListRuinMoveSelector(_) => {
801 panic!("list move selector configured against a standard-variable stock context");
802 }
803 MoveSelectorConfig::CartesianProductMoveSelector(_) => {
804 panic!("cartesian_product move selectors are not supported in stock solving");
805 }
806 }
807 }
808
809 match config {
810 Some(cfg) => collect(cfg, descriptor, &bindings, &mut leaves),
811 None => {
812 for binding in bindings {
813 leaves.push(DescriptorLeafSelector::Change(
814 DescriptorChangeMoveSelector::new(binding.clone(), descriptor.clone()),
815 ));
816 leaves.push(DescriptorLeafSelector::Swap(
817 DescriptorSwapMoveSelector::new(binding, descriptor.clone()),
818 ));
819 }
820 }
821 }
822
823 assert!(
824 !leaves.is_empty(),
825 "stock move selector configuration produced no standard neighborhoods"
826 );
827
828 leaves
829}
830
831pub fn build_descriptor_move_selector<S>(
832 config: Option<&MoveSelectorConfig>,
833 descriptor: &SolutionDescriptor,
834) -> VecUnionSelector<S, DescriptorEitherMove<S>, DescriptorLeafSelector<S>>
835where
836 S: solverforge_core::domain::PlanningSolution + 'static,
837{
838 VecUnionSelector::new(collect_descriptor_leaf_selectors(config, descriptor))
839}
840
841pub fn build_descriptor_construction<S>(
842 config: Option<&ConstructionHeuristicConfig>,
843 descriptor: &SolutionDescriptor,
844) -> DescriptorConstruction<S>
845where
846 S: solverforge_core::domain::PlanningSolution + 'static,
847{
848 let bindings = config
849 .map(|cfg| {
850 let matched = find_binding(
851 &collect_bindings(descriptor),
852 cfg.target.entity_class.as_deref(),
853 cfg.target.variable_name.as_deref(),
854 );
855 assert!(
856 !matched.is_empty(),
857 "construction heuristic matched no standard planning variables for entity_class={:?} variable_name={:?}",
858 cfg.target.entity_class,
859 cfg.target.variable_name
860 );
861 matched
862 })
863 .unwrap_or_else(|| collect_bindings(descriptor));
864 let placer = DescriptorEntityPlacer::new(bindings, descriptor.clone());
865 let construction_type = config
866 .map(|cfg| cfg.construction_heuristic_type)
867 .unwrap_or(ConstructionHeuristicType::FirstFit);
868
869 match construction_type {
870 ConstructionHeuristicType::FirstFit => DescriptorConstruction::FirstFit(
871 ConstructionHeuristicPhase::new(placer, FirstFitForager::new()),
872 ),
873 ConstructionHeuristicType::CheapestInsertion => DescriptorConstruction::BestFit(
874 ConstructionHeuristicPhase::new(placer, BestFitForager::new()),
875 ),
876 ConstructionHeuristicType::FirstFitDecreasing
877 | ConstructionHeuristicType::WeakestFit
878 | ConstructionHeuristicType::WeakestFitDecreasing
879 | ConstructionHeuristicType::StrongestFit
880 | ConstructionHeuristicType::StrongestFitDecreasing
881 | ConstructionHeuristicType::AllocateEntityFromQueue
882 | ConstructionHeuristicType::AllocateToValueFromQueue
883 | ConstructionHeuristicType::ListRoundRobin
884 | ConstructionHeuristicType::ListCheapestInsertion
885 | ConstructionHeuristicType::ListRegretInsertion
886 | ConstructionHeuristicType::ListClarkeWright
887 | ConstructionHeuristicType::ListKOpt => {
888 panic!(
889 "descriptor standard construction does not support {:?}",
890 construction_type
891 );
892 }
893 }
894}