Skip to main content

solverforge_solver/descriptor/selectors/dispatch/
build.rs

1fn build_descriptor_flat_selector<S>(
2    config: Option<&MoveSelectorConfig>,
3    descriptor: &SolutionDescriptor,
4    random_seed: Option<u64>,
5) -> DescriptorFlatSelector<S>
6where
7    S: PlanningSolution + 'static,
8    S::Score: Score,
9{
10    let bindings = collect_bindings(descriptor);
11    let mut leaves = Vec::new();
12
13    fn require_matches<S>(
14        label: &str,
15        entity_class: Option<&str>,
16        variable_name: Option<&str>,
17        matched: &[VariableBinding],
18    ) where
19        S: PlanningSolution + 'static,
20        S::Score: Score,
21    {
22        assert!(
23            !matched.is_empty(),
24            "{label} selector matched no scalar planning variables for entity_class={:?} variable_name={:?}",
25            entity_class,
26            variable_name,
27        );
28    }
29
30    fn collect<S>(
31        cfg: &MoveSelectorConfig,
32        descriptor: &SolutionDescriptor,
33        bindings: &[VariableBinding],
34        random_seed: Option<u64>,
35        leaves: &mut Vec<DescriptorLeafSelector<S>>,
36    ) where
37        S: PlanningSolution + 'static,
38        S::Score: Score,
39    {
40        match cfg {
41            MoveSelectorConfig::ChangeMoveSelector(change) => {
42                let matched = find_binding(
43                    bindings,
44                    change.target.entity_class.as_deref(),
45                    change.target.variable_name.as_deref(),
46                );
47                require_matches::<S>(
48                    "change_move",
49                    change.target.entity_class.as_deref(),
50                    change.target.variable_name.as_deref(),
51                    &matched,
52                );
53                for binding in matched {
54                    leaves.push(DescriptorLeafSelector::Change(
55                        DescriptorChangeMoveSelector::new(
56                            binding,
57                            descriptor.clone(),
58                            change.value_candidate_limit,
59                        ),
60                    ));
61                }
62            }
63            MoveSelectorConfig::SwapMoveSelector(swap) => {
64                let matched = find_binding(
65                    bindings,
66                    swap.target.entity_class.as_deref(),
67                    swap.target.variable_name.as_deref(),
68                );
69                require_matches::<S>(
70                    "swap_move",
71                    swap.target.entity_class.as_deref(),
72                    swap.target.variable_name.as_deref(),
73                    &matched,
74                );
75                for binding in matched {
76                    leaves.push(DescriptorLeafSelector::Swap(
77                        DescriptorSwapMoveSelector::new(binding, descriptor.clone()),
78                    ));
79                }
80            }
81            MoveSelectorConfig::NearbyChangeMoveSelector(nearby_change) => {
82                let matched = find_binding(
83                    bindings,
84                    nearby_change.target.entity_class.as_deref(),
85                    nearby_change.target.variable_name.as_deref(),
86                );
87                require_matches::<S>(
88                    "nearby_change_move",
89                    nearby_change.target.entity_class.as_deref(),
90                    nearby_change.target.variable_name.as_deref(),
91                    &matched,
92                );
93                for binding in matched {
94                    assert!(
95                        binding.nearby_value_candidates.is_some(),
96                        "nearby_change_move selector requires nearby_value_candidates for {}::{}",
97                        binding.entity_type_name,
98                        binding.variable_name,
99                    );
100                    leaves.push(DescriptorLeafSelector::NearbyChange(
101                        DescriptorNearbyChangeMoveSelector {
102                            binding,
103                            solution_descriptor: descriptor.clone(),
104                            max_nearby: nearby_change.max_nearby,
105                            value_candidate_limit: nearby_change.value_candidate_limit,
106                            _phantom: PhantomData,
107                        },
108                    ));
109                }
110            }
111            MoveSelectorConfig::NearbySwapMoveSelector(nearby_swap) => {
112                let matched = find_binding(
113                    bindings,
114                    nearby_swap.target.entity_class.as_deref(),
115                    nearby_swap.target.variable_name.as_deref(),
116                );
117                require_matches::<S>(
118                    "nearby_swap_move",
119                    nearby_swap.target.entity_class.as_deref(),
120                    nearby_swap.target.variable_name.as_deref(),
121                    &matched,
122                );
123                for binding in matched {
124                    assert!(
125                        binding.nearby_entity_candidates.is_some(),
126                        "nearby_swap_move selector requires nearby_entity_candidates for {}::{}",
127                        binding.entity_type_name,
128                        binding.variable_name,
129                    );
130                    leaves.push(DescriptorLeafSelector::NearbySwap(
131                        DescriptorNearbySwapMoveSelector {
132                            binding,
133                            solution_descriptor: descriptor.clone(),
134                            max_nearby: nearby_swap.max_nearby,
135                            _phantom: PhantomData,
136                        },
137                    ));
138                }
139            }
140            MoveSelectorConfig::PillarChangeMoveSelector(pillar_change) => {
141                let matched = find_binding(
142                    bindings,
143                    pillar_change.target.entity_class.as_deref(),
144                    pillar_change.target.variable_name.as_deref(),
145                );
146                require_matches::<S>(
147                    "pillar_change_move",
148                    pillar_change.target.entity_class.as_deref(),
149                    pillar_change.target.variable_name.as_deref(),
150                    &matched,
151                );
152                for binding in matched {
153                    leaves.push(DescriptorLeafSelector::PillarChange(
154                        DescriptorPillarChangeMoveSelector {
155                            binding,
156                            solution_descriptor: descriptor.clone(),
157                            minimum_sub_pillar_size: pillar_change.minimum_sub_pillar_size,
158                            maximum_sub_pillar_size: pillar_change.maximum_sub_pillar_size,
159                            value_candidate_limit: pillar_change.value_candidate_limit,
160                            _phantom: PhantomData,
161                        },
162                    ));
163                }
164            }
165            MoveSelectorConfig::PillarSwapMoveSelector(pillar_swap) => {
166                let matched = find_binding(
167                    bindings,
168                    pillar_swap.target.entity_class.as_deref(),
169                    pillar_swap.target.variable_name.as_deref(),
170                );
171                require_matches::<S>(
172                    "pillar_swap_move",
173                    pillar_swap.target.entity_class.as_deref(),
174                    pillar_swap.target.variable_name.as_deref(),
175                    &matched,
176                );
177                for binding in matched {
178                    leaves.push(DescriptorLeafSelector::PillarSwap(
179                        DescriptorPillarSwapMoveSelector {
180                            binding,
181                            solution_descriptor: descriptor.clone(),
182                            minimum_sub_pillar_size: pillar_swap.minimum_sub_pillar_size,
183                            maximum_sub_pillar_size: pillar_swap.maximum_sub_pillar_size,
184                            _phantom: PhantomData,
185                        },
186                    ));
187                }
188            }
189            MoveSelectorConfig::RuinRecreateMoveSelector(ruin_recreate) => {
190                validate_ruin_recreate_bounds(
191                    ruin_recreate.min_ruin_count,
192                    ruin_recreate.max_ruin_count,
193                );
194                let matched = find_binding(
195                    bindings,
196                    ruin_recreate.target.entity_class.as_deref(),
197                    ruin_recreate.target.variable_name.as_deref(),
198                );
199                require_matches::<S>(
200                    "ruin_recreate_move",
201                    ruin_recreate.target.entity_class.as_deref(),
202                    ruin_recreate.target.variable_name.as_deref(),
203                    &matched,
204                );
205                for binding in matched {
206                    if ruin_recreate.recreate_heuristic_type == RecreateHeuristicType::CheapestInsertion {
207                        assert!(
208                            binding.candidate_values.is_some()
209                                || ruin_recreate.value_candidate_limit.is_some(),
210                            "cheapest_insertion descriptor-driven ruin_recreate requires candidate_values or value_candidate_limit for {}::{}",
211                            binding.entity_type_name,
212                            binding.variable_name,
213                        );
214                    }
215                    let rng = match scoped_seed(
216                        random_seed,
217                        binding.descriptor_index,
218                        binding.variable_name,
219                        "descriptor_ruin_recreate_move_selector",
220                    ) {
221                        Some(seed) => SmallRng::seed_from_u64(seed),
222                        None => SmallRng::from_rng(&mut rand::rng()),
223                    };
224                    leaves.push(DescriptorLeafSelector::RuinRecreate(
225                        DescriptorRuinRecreateMoveSelector {
226                            binding,
227                            solution_descriptor: descriptor.clone(),
228                            min_ruin_count: ruin_recreate.min_ruin_count,
229                            max_ruin_count: ruin_recreate.max_ruin_count,
230                            moves_per_step: ruin_recreate.moves_per_step.unwrap_or(10).max(1),
231                            value_candidate_limit: ruin_recreate.value_candidate_limit,
232                            recreate_heuristic_type: ruin_recreate.recreate_heuristic_type,
233                            rng: RefCell::new(rng),
234                            _phantom: PhantomData,
235                        },
236                    ));
237                }
238            }
239            MoveSelectorConfig::UnionMoveSelector(union) => {
240                for child in &union.selectors {
241                    collect::<S>(child, descriptor, bindings, random_seed, leaves);
242                }
243            }
244            MoveSelectorConfig::LimitedNeighborhood(_) => {
245                panic!("limited_neighborhood must be handled by the canonical runtime");
246            }
247            MoveSelectorConfig::ListChangeMoveSelector(_)
248            | MoveSelectorConfig::NearbyListChangeMoveSelector(_)
249            | MoveSelectorConfig::ListSwapMoveSelector(_)
250            | MoveSelectorConfig::NearbyListSwapMoveSelector(_)
251            | MoveSelectorConfig::SublistChangeMoveSelector(_)
252            | MoveSelectorConfig::SublistSwapMoveSelector(_)
253            | MoveSelectorConfig::ListReverseMoveSelector(_)
254            | MoveSelectorConfig::KOptMoveSelector(_)
255            | MoveSelectorConfig::ListRuinMoveSelector(_) => {
256                panic!("list move selector configured against a scalar-variable model");
257            }
258            MoveSelectorConfig::CartesianProductMoveSelector(_) => {
259                panic!(
260                    "nested cartesian_product move selectors are not supported inside descriptor cartesian children"
261                );
262            }
263            MoveSelectorConfig::ConflictRepairMoveSelector(_) => {
264                panic!("conflict_repair_move_selector must be handled by the canonical runtime");
265            }
266            MoveSelectorConfig::CompoundConflictRepairMoveSelector(_) => {
267                panic!(
268                    "compound_conflict_repair_move_selector must be handled by the canonical runtime"
269                );
270            }
271            MoveSelectorConfig::GroupedScalarMoveSelector(_) => {
272                panic!("grouped_scalar_move_selector must be handled by the canonical runtime");
273            }
274        }
275    }
276
277    match config {
278        Some(cfg) => collect::<S>(cfg, descriptor, &bindings, random_seed, &mut leaves),
279        None => {
280            for binding in bindings {
281                leaves.push(DescriptorLeafSelector::Change(
282                    DescriptorChangeMoveSelector::new(binding.clone(), descriptor.clone(), None),
283                ));
284                leaves.push(DescriptorLeafSelector::Swap(
285                    DescriptorSwapMoveSelector::new(binding, descriptor.clone()),
286                ));
287            }
288        }
289    }
290
291    assert!(
292        !leaves.is_empty(),
293        "move selector configuration produced no scalar neighborhoods"
294    );
295
296    let selection_order = match config {
297        Some(MoveSelectorConfig::UnionMoveSelector(union)) => union.selection_order,
298        _ => solverforge_config::UnionSelectionOrder::Sequential,
299    };
300    VecUnionSelector::with_selection_order(leaves, selection_order)
301}
302
303pub fn build_descriptor_move_selector<S>(
304    config: Option<&MoveSelectorConfig>,
305    descriptor: &SolutionDescriptor,
306    random_seed: Option<u64>,
307) -> DescriptorSelector<S>
308where
309    S: PlanningSolution + 'static,
310    S::Score: Score,
311{
312    fn selector_requires_score_during_move(config: &MoveSelectorConfig) -> bool {
313        match config {
314            MoveSelectorConfig::RuinRecreateMoveSelector(_) => true,
315            MoveSelectorConfig::LimitedNeighborhood(limit) => {
316                selector_requires_score_during_move(limit.selector.as_ref())
317            }
318            MoveSelectorConfig::UnionMoveSelector(union) => union
319                .selectors
320                .iter()
321                .any(selector_requires_score_during_move),
322            MoveSelectorConfig::CartesianProductMoveSelector(_) => true,
323            _ => false,
324        }
325    }
326
327    fn assert_cartesian_left_preview_safe(config: &MoveSelectorConfig) {
328        assert!(
329            !selector_requires_score_during_move(config),
330            "cartesian_product left child cannot contain ruin_recreate_move_selector because preview directors do not calculate scores",
331        );
332    }
333
334    fn collect_nodes<S>(
335        config: Option<&MoveSelectorConfig>,
336        descriptor: &SolutionDescriptor,
337        random_seed: Option<u64>,
338        nodes: &mut Vec<DescriptorSelectorNode<S>>,
339    ) where
340        S: PlanningSolution + 'static,
341        S::Score: Score,
342    {
343        match config {
344            Some(MoveSelectorConfig::UnionMoveSelector(union)) => {
345                for child in &union.selectors {
346                    collect_nodes::<S>(Some(child), descriptor, random_seed, nodes);
347                }
348            }
349            Some(MoveSelectorConfig::CartesianProductMoveSelector(cartesian)) => {
350                assert_eq!(
351                    cartesian.selectors.len(),
352                    2,
353                    "cartesian_product move selector requires exactly two child selectors"
354                );
355                assert_cartesian_left_preview_safe(&cartesian.selectors[0]);
356                let left = build_descriptor_flat_selector::<S>(
357                    Some(&cartesian.selectors[0]),
358                    descriptor,
359                    random_seed,
360                );
361                let right = build_descriptor_flat_selector::<S>(
362                    Some(&cartesian.selectors[1]),
363                    descriptor,
364                    random_seed,
365                );
366                nodes.push(DescriptorSelectorNode::Cartesian(
367                    CartesianProductSelector::new(left, right, wrap_descriptor_composite::<S>)
368                        .with_require_hard_improvement(cartesian.require_hard_improvement),
369                ));
370            }
371            other => {
372                let flat = build_descriptor_flat_selector::<S>(other, descriptor, random_seed);
373                nodes.extend(
374                    flat.into_selectors()
375                        .into_iter()
376                        .map(DescriptorSelectorNode::Leaf),
377                );
378            }
379        }
380    }
381
382    let mut nodes = Vec::new();
383    collect_nodes::<S>(config, descriptor, random_seed, &mut nodes);
384    assert!(
385        !nodes.is_empty(),
386        "move selector configuration produced no scalar neighborhoods"
387    );
388    let selection_order = match config {
389        Some(MoveSelectorConfig::UnionMoveSelector(union)) => union.selection_order,
390        _ => solverforge_config::UnionSelectionOrder::Sequential,
391    };
392    VecUnionSelector::with_selection_order(nodes, selection_order)
393}