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 mut moves = Vec::new();
410 for entity_index in 0..count {
411 let entity = self
412 .solution_descriptor
413 .get_entity(
414 score_director.working_solution() as &dyn Any,
415 self.binding.descriptor_index,
416 entity_index,
417 )
418 .expect("entity lookup failed for change selector");
419 for value in self.binding.values_for_entity(
420 &self.solution_descriptor,
421 score_director.working_solution() as &dyn Any,
422 entity,
423 ) {
424 moves.push(DescriptorEitherMove::Change(DescriptorChangeMove::new(
425 self.binding.clone(),
426 entity_index,
427 Some(value),
428 self.solution_descriptor.clone(),
429 )));
430 }
431 }
432 moves.into_iter()
433 }
434
435 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
436 let count = score_director
437 .entity_count(self.binding.descriptor_index)
438 .unwrap_or(0);
439 let mut total = 0;
440 for entity_index in 0..count {
441 let entity = self
442 .solution_descriptor
443 .get_entity(
444 score_director.working_solution() as &dyn Any,
445 self.binding.descriptor_index,
446 entity_index,
447 )
448 .expect("entity lookup failed for change selector");
449 total += self
450 .binding
451 .values_for_entity(
452 &self.solution_descriptor,
453 score_director.working_solution() as &dyn Any,
454 entity,
455 )
456 .len();
457 }
458 total
459 }
460}
461
462#[derive(Clone)]
463pub struct DescriptorSwapMoveSelector<S> {
464 binding: VariableBinding,
465 solution_descriptor: SolutionDescriptor,
466 _phantom: PhantomData<fn() -> S>,
467}
468
469impl<S> Debug for DescriptorSwapMoveSelector<S> {
470 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
471 f.debug_struct("DescriptorSwapMoveSelector")
472 .field("binding", &self.binding)
473 .finish()
474 }
475}
476
477impl<S> DescriptorSwapMoveSelector<S> {
478 fn new(binding: VariableBinding, solution_descriptor: SolutionDescriptor) -> Self {
479 Self {
480 binding,
481 solution_descriptor,
482 _phantom: PhantomData,
483 }
484 }
485}
486
487impl<S> MoveSelector<S, DescriptorEitherMove<S>> for DescriptorSwapMoveSelector<S>
488where
489 S: solverforge_core::domain::PlanningSolution + 'static,
490{
491 fn iter_moves<'a, D: Director<S>>(
492 &'a self,
493 score_director: &'a D,
494 ) -> impl Iterator<Item = DescriptorEitherMove<S>> + 'a {
495 let count = score_director
496 .entity_count(self.binding.descriptor_index)
497 .unwrap_or(0);
498 let mut moves = Vec::new();
499 for left_entity_index in 0..count {
500 for right_entity_index in (left_entity_index + 1)..count {
501 moves.push(DescriptorEitherMove::Swap(DescriptorSwapMove::new(
502 self.binding.clone(),
503 left_entity_index,
504 right_entity_index,
505 self.solution_descriptor.clone(),
506 )));
507 }
508 }
509 moves.into_iter()
510 }
511
512 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
513 let count = score_director
514 .entity_count(self.binding.descriptor_index)
515 .unwrap_or(0);
516 count.saturating_mul(count.saturating_sub(1)) / 2
517 }
518}
519
520#[derive(Clone)]
521pub enum DescriptorLeafSelector<S> {
522 Change(DescriptorChangeMoveSelector<S>),
523 Swap(DescriptorSwapMoveSelector<S>),
524}
525
526impl<S> Debug for DescriptorLeafSelector<S>
527where
528 S: solverforge_core::domain::PlanningSolution,
529{
530 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
531 match self {
532 Self::Change(selector) => selector.fmt(f),
533 Self::Swap(selector) => selector.fmt(f),
534 }
535 }
536}
537
538impl<S> MoveSelector<S, DescriptorEitherMove<S>> for DescriptorLeafSelector<S>
539where
540 S: solverforge_core::domain::PlanningSolution + 'static,
541{
542 fn iter_moves<'a, D: Director<S>>(
543 &'a self,
544 score_director: &'a D,
545 ) -> impl Iterator<Item = DescriptorEitherMove<S>> + 'a {
546 let moves: Vec<_> = match self {
547 Self::Change(selector) => selector.iter_moves(score_director).collect(),
548 Self::Swap(selector) => selector.iter_moves(score_director).collect(),
549 };
550 moves.into_iter()
551 }
552
553 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
554 match self {
555 Self::Change(selector) => selector.size(score_director),
556 Self::Swap(selector) => selector.size(score_director),
557 }
558 }
559}
560
561pub enum DescriptorConstruction<S: solverforge_core::domain::PlanningSolution> {
562 FirstFit(
563 ConstructionHeuristicPhase<
564 S,
565 DescriptorEitherMove<S>,
566 DescriptorEntityPlacer<S>,
567 FirstFitForager<S, DescriptorEitherMove<S>>,
568 >,
569 ),
570 BestFit(
571 ConstructionHeuristicPhase<
572 S,
573 DescriptorEitherMove<S>,
574 DescriptorEntityPlacer<S>,
575 BestFitForager<S, DescriptorEitherMove<S>>,
576 >,
577 ),
578}
579
580impl<S: solverforge_core::domain::PlanningSolution> Debug for DescriptorConstruction<S> {
581 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582 match self {
583 Self::FirstFit(phase) => write!(f, "DescriptorConstruction::FirstFit({phase:?})"),
584 Self::BestFit(phase) => write!(f, "DescriptorConstruction::BestFit({phase:?})"),
585 }
586 }
587}
588
589impl<S, D, ProgressCb> crate::phase::Phase<S, D, ProgressCb> for DescriptorConstruction<S>
590where
591 S: solverforge_core::domain::PlanningSolution + 'static,
592 D: Director<S>,
593 ProgressCb: ProgressCallback<S>,
594{
595 fn solve(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
596 match self {
597 Self::FirstFit(phase) => phase.solve(solver_scope),
598 Self::BestFit(phase) => phase.solve(solver_scope),
599 }
600 }
601
602 fn phase_type_name(&self) -> &'static str {
603 "DescriptorConstruction"
604 }
605}
606
607#[derive(Clone)]
608pub struct DescriptorEntityPlacer<S> {
609 bindings: Vec<VariableBinding>,
610 solution_descriptor: SolutionDescriptor,
611 _phantom: PhantomData<fn() -> S>,
612}
613
614impl<S> Debug for DescriptorEntityPlacer<S> {
615 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616 f.debug_struct("DescriptorEntityPlacer")
617 .field("bindings", &self.bindings)
618 .finish()
619 }
620}
621
622impl<S> DescriptorEntityPlacer<S> {
623 fn new(bindings: Vec<VariableBinding>, solution_descriptor: SolutionDescriptor) -> Self {
624 Self {
625 bindings,
626 solution_descriptor,
627 _phantom: PhantomData,
628 }
629 }
630}
631
632impl<S> EntityPlacer<S, DescriptorEitherMove<S>> for DescriptorEntityPlacer<S>
633where
634 S: solverforge_core::domain::PlanningSolution + 'static,
635{
636 fn get_placements<D: Director<S>>(
637 &self,
638 score_director: &D,
639 ) -> Vec<Placement<S, DescriptorEitherMove<S>>> {
640 let mut placements = Vec::new();
641
642 for binding in &self.bindings {
643 let count = score_director
644 .entity_count(binding.descriptor_index)
645 .unwrap_or(0);
646
647 for entity_index in 0..count {
648 let entity = self
649 .solution_descriptor
650 .get_entity(
651 score_director.working_solution() as &dyn Any,
652 binding.descriptor_index,
653 entity_index,
654 )
655 .expect("entity lookup failed for descriptor construction");
656 let current_value = (binding.getter)(entity);
657 if current_value.is_some() {
658 continue;
659 }
660
661 let moves = binding
662 .values_for_entity(
663 &self.solution_descriptor,
664 score_director.working_solution() as &dyn Any,
665 entity,
666 )
667 .into_iter()
668 .map(|value| {
669 DescriptorEitherMove::Change(DescriptorChangeMove::new(
670 binding.clone(),
671 entity_index,
672 Some(value),
673 self.solution_descriptor.clone(),
674 ))
675 })
676 .collect::<Vec<_>>();
677
678 if moves.is_empty() {
679 continue;
680 }
681
682 placements.push(Placement::new(
683 EntityReference::new(binding.descriptor_index, entity_index),
684 moves,
685 ));
686 }
687 }
688
689 placements
690 }
691}
692
693fn collect_bindings(descriptor: &SolutionDescriptor) -> Vec<VariableBinding> {
694 let mut bindings = Vec::new();
695 for (descriptor_index, entity_descriptor) in descriptor.entity_descriptors.iter().enumerate() {
696 for variable in entity_descriptor.genuine_variable_descriptors() {
697 let Some(getter) = variable.usize_getter else {
698 continue;
699 };
700 let Some(setter) = variable.usize_setter else {
701 continue;
702 };
703 bindings.push(VariableBinding {
704 descriptor_index,
705 entity_type_name: entity_descriptor.type_name,
706 variable_name: variable.name,
707 getter,
708 setter,
709 value_range_provider: variable.value_range_provider,
710 provider: variable.entity_value_provider,
711 range_type: variable.value_range_type.clone(),
712 });
713 }
714 }
715 bindings
716}
717
718fn find_binding(
719 bindings: &[VariableBinding],
720 entity_class: Option<&str>,
721 variable_name: Option<&str>,
722) -> Vec<VariableBinding> {
723 bindings
724 .iter()
725 .filter(|binding| entity_class.is_none_or(|name| name == binding.entity_type_name))
726 .filter(|binding| variable_name.is_none_or(|name| name == binding.variable_name))
727 .cloned()
728 .collect()
729}
730
731pub fn descriptor_has_bindings(descriptor: &SolutionDescriptor) -> bool {
732 !collect_bindings(descriptor).is_empty()
733}
734
735pub fn standard_work_remaining<S>(
736 descriptor: &SolutionDescriptor,
737 entity_class: Option<&str>,
738 variable_name: Option<&str>,
739 solution: &S,
740) -> bool
741where
742 S: solverforge_core::domain::PlanningSolution + 'static,
743{
744 let bindings = find_binding(&collect_bindings(descriptor), entity_class, variable_name);
745 for binding in bindings {
746 let Some(entity_count) = descriptor
747 .entity_descriptors
748 .get(binding.descriptor_index)
749 .and_then(|entity| entity.entity_count(solution as &dyn Any))
750 else {
751 continue;
752 };
753 for entity_index in 0..entity_count {
754 let entity = descriptor
755 .get_entity(solution as &dyn Any, binding.descriptor_index, entity_index)
756 .expect("entity lookup failed while checking standard work");
757 if (binding.getter)(entity).is_none()
758 && !binding
759 .values_for_entity(descriptor, solution as &dyn Any, entity)
760 .is_empty()
761 {
762 return true;
763 }
764 }
765 }
766 false
767}
768
769pub fn standard_target_matches(
770 descriptor: &SolutionDescriptor,
771 entity_class: Option<&str>,
772 variable_name: Option<&str>,
773) -> bool {
774 !find_binding(&collect_bindings(descriptor), entity_class, variable_name).is_empty()
775}
776
777fn collect_descriptor_leaf_selectors<S>(
778 config: Option<&MoveSelectorConfig>,
779 descriptor: &SolutionDescriptor,
780) -> Vec<DescriptorLeafSelector<S>>
781where
782 S: solverforge_core::domain::PlanningSolution + 'static,
783{
784 let bindings = collect_bindings(descriptor);
785 let mut leaves = Vec::new();
786
787 fn collect<S>(
788 cfg: &MoveSelectorConfig,
789 descriptor: &SolutionDescriptor,
790 bindings: &[VariableBinding],
791 leaves: &mut Vec<DescriptorLeafSelector<S>>,
792 ) where
793 S: solverforge_core::domain::PlanningSolution + 'static,
794 {
795 match cfg {
796 MoveSelectorConfig::ChangeMoveSelector(change) => {
797 let matched = find_binding(
798 bindings,
799 change.target.entity_class.as_deref(),
800 change.target.variable_name.as_deref(),
801 );
802 assert!(
803 !matched.is_empty(),
804 "change_move selector matched no standard planning variables for entity_class={:?} variable_name={:?}",
805 change.target.entity_class,
806 change.target.variable_name
807 );
808 for binding in matched {
809 leaves.push(DescriptorLeafSelector::Change(
810 DescriptorChangeMoveSelector::new(binding, descriptor.clone()),
811 ));
812 }
813 }
814 MoveSelectorConfig::SwapMoveSelector(swap) => {
815 let matched = find_binding(
816 bindings,
817 swap.target.entity_class.as_deref(),
818 swap.target.variable_name.as_deref(),
819 );
820 assert!(
821 !matched.is_empty(),
822 "swap_move selector matched no standard planning variables for entity_class={:?} variable_name={:?}",
823 swap.target.entity_class,
824 swap.target.variable_name
825 );
826 for binding in matched {
827 leaves.push(DescriptorLeafSelector::Swap(
828 DescriptorSwapMoveSelector::new(binding, descriptor.clone()),
829 ));
830 }
831 }
832 MoveSelectorConfig::UnionMoveSelector(union) => {
833 for child in &union.selectors {
834 collect(child, descriptor, bindings, leaves);
835 }
836 }
837 MoveSelectorConfig::ListChangeMoveSelector(_)
838 | MoveSelectorConfig::NearbyListChangeMoveSelector(_)
839 | MoveSelectorConfig::ListSwapMoveSelector(_)
840 | MoveSelectorConfig::NearbyListSwapMoveSelector(_)
841 | MoveSelectorConfig::SubListChangeMoveSelector(_)
842 | MoveSelectorConfig::SubListSwapMoveSelector(_)
843 | MoveSelectorConfig::ListReverseMoveSelector(_)
844 | MoveSelectorConfig::KOptMoveSelector(_)
845 | MoveSelectorConfig::ListRuinMoveSelector(_) => {
846 panic!("list move selector configured against a standard-variable stock context");
847 }
848 MoveSelectorConfig::CartesianProductMoveSelector(_) => {
849 panic!("cartesian_product move selectors are not supported in stock solving");
850 }
851 }
852 }
853
854 match config {
855 Some(cfg) => collect(cfg, descriptor, &bindings, &mut leaves),
856 None => {
857 for binding in bindings {
858 leaves.push(DescriptorLeafSelector::Change(
859 DescriptorChangeMoveSelector::new(binding.clone(), descriptor.clone()),
860 ));
861 leaves.push(DescriptorLeafSelector::Swap(
862 DescriptorSwapMoveSelector::new(binding, descriptor.clone()),
863 ));
864 }
865 }
866 }
867
868 assert!(
869 !leaves.is_empty(),
870 "stock move selector configuration produced no standard neighborhoods"
871 );
872
873 leaves
874}
875
876pub fn build_descriptor_move_selector<S>(
877 config: Option<&MoveSelectorConfig>,
878 descriptor: &SolutionDescriptor,
879) -> VecUnionSelector<S, DescriptorEitherMove<S>, DescriptorLeafSelector<S>>
880where
881 S: solverforge_core::domain::PlanningSolution + 'static,
882{
883 VecUnionSelector::new(collect_descriptor_leaf_selectors(config, descriptor))
884}
885
886pub fn build_descriptor_construction<S>(
887 config: Option<&ConstructionHeuristicConfig>,
888 descriptor: &SolutionDescriptor,
889) -> DescriptorConstruction<S>
890where
891 S: solverforge_core::domain::PlanningSolution + 'static,
892{
893 let bindings = config
894 .map(|cfg| {
895 let matched = find_binding(
896 &collect_bindings(descriptor),
897 cfg.target.entity_class.as_deref(),
898 cfg.target.variable_name.as_deref(),
899 );
900 assert!(
901 !matched.is_empty(),
902 "construction heuristic matched no standard planning variables for entity_class={:?} variable_name={:?}",
903 cfg.target.entity_class,
904 cfg.target.variable_name
905 );
906 matched
907 })
908 .unwrap_or_else(|| collect_bindings(descriptor));
909 let placer = DescriptorEntityPlacer::new(bindings, descriptor.clone());
910 let construction_type = config
911 .map(|cfg| cfg.construction_heuristic_type)
912 .unwrap_or(ConstructionHeuristicType::FirstFit);
913
914 match construction_type {
915 ConstructionHeuristicType::FirstFit => DescriptorConstruction::FirstFit(
916 ConstructionHeuristicPhase::new(placer, FirstFitForager::new()),
917 ),
918 ConstructionHeuristicType::CheapestInsertion => DescriptorConstruction::BestFit(
919 ConstructionHeuristicPhase::new(placer, BestFitForager::new()),
920 ),
921 ConstructionHeuristicType::FirstFitDecreasing
922 | ConstructionHeuristicType::WeakestFit
923 | ConstructionHeuristicType::WeakestFitDecreasing
924 | ConstructionHeuristicType::StrongestFit
925 | ConstructionHeuristicType::StrongestFitDecreasing
926 | ConstructionHeuristicType::AllocateEntityFromQueue
927 | ConstructionHeuristicType::AllocateToValueFromQueue
928 | ConstructionHeuristicType::ListRoundRobin
929 | ConstructionHeuristicType::ListCheapestInsertion
930 | ConstructionHeuristicType::ListRegretInsertion
931 | ConstructionHeuristicType::ListClarkeWright
932 | ConstructionHeuristicType::ListKOpt => {
933 panic!(
934 "descriptor standard construction does not support {:?}",
935 construction_type
936 );
937 }
938 }
939}
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944 use solverforge_core::domain::{
945 EntityCollectionExtractor, EntityDescriptor, PlanningSolution, ProblemFactDescriptor,
946 VariableDescriptor,
947 };
948 use solverforge_core::score::SoftScore;
949 use solverforge_scoring::ScoreDirector;
950 use std::any::{Any, TypeId};
951
952 #[derive(Clone, Debug)]
953 struct Worker;
954
955 #[derive(Clone, Debug)]
956 struct Task {
957 worker_idx: Option<usize>,
958 }
959
960 #[derive(Clone, Debug)]
961 struct Plan {
962 workers: Vec<Worker>,
963 tasks: Vec<Task>,
964 score: Option<SoftScore>,
965 }
966
967 impl PlanningSolution for Plan {
968 type Score = SoftScore;
969
970 fn score(&self) -> Option<Self::Score> {
971 self.score
972 }
973
974 fn set_score(&mut self, score: Option<Self::Score>) {
975 self.score = score;
976 }
977 }
978
979 fn get_worker_idx(entity: &dyn Any) -> Option<usize> {
980 entity
981 .downcast_ref::<Task>()
982 .expect("task expected")
983 .worker_idx
984 }
985
986 fn set_worker_idx(entity: &mut dyn Any, value: Option<usize>) {
987 entity
988 .downcast_mut::<Task>()
989 .expect("task expected")
990 .worker_idx = value;
991 }
992
993 fn descriptor() -> SolutionDescriptor {
994 SolutionDescriptor::new("Plan", TypeId::of::<Plan>())
995 .with_entity(
996 EntityDescriptor::new("Task", TypeId::of::<Task>(), "tasks")
997 .with_extractor(Box::new(EntityCollectionExtractor::new(
998 "Task",
999 "tasks",
1000 |s: &Plan| &s.tasks,
1001 |s: &mut Plan| &mut s.tasks,
1002 )))
1003 .with_variable(
1004 VariableDescriptor::genuine("worker_idx")
1005 .with_allows_unassigned(true)
1006 .with_value_range("workers")
1007 .with_usize_accessors(get_worker_idx, set_worker_idx),
1008 ),
1009 )
1010 .with_problem_fact(
1011 ProblemFactDescriptor::new("Worker", TypeId::of::<Worker>(), "workers")
1012 .with_extractor(Box::new(EntityCollectionExtractor::new(
1013 "Worker",
1014 "workers",
1015 |s: &Plan| &s.workers,
1016 |s: &mut Plan| &mut s.workers,
1017 ))),
1018 )
1019 }
1020
1021 #[test]
1022 fn solution_level_value_range_generates_standard_work() {
1023 let descriptor = descriptor();
1024 let plan = Plan {
1025 workers: vec![Worker, Worker, Worker],
1026 tasks: vec![Task { worker_idx: None }],
1027 score: None,
1028 };
1029
1030 assert!(standard_work_remaining(
1031 &descriptor,
1032 Some("Task"),
1033 Some("worker_idx"),
1034 &plan
1035 ));
1036 }
1037
1038 #[test]
1039 fn solution_level_value_range_builds_change_moves() {
1040 let descriptor = descriptor();
1041 let plan = Plan {
1042 workers: vec![Worker, Worker, Worker],
1043 tasks: vec![Task { worker_idx: None }],
1044 score: None,
1045 };
1046 let director = ScoreDirector::simple(plan, descriptor.clone(), |s, _| s.tasks.len());
1047 let selector = build_descriptor_move_selector::<Plan>(None, &descriptor);
1048
1049 assert_eq!(selector.size(&director), 3);
1051 }
1052}