Skip to main content

solverforge_config/
move_selector.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
4#[serde(rename_all = "snake_case")]
5pub struct VariableTargetConfig {
6    pub entity_class: Option<String>,
7    pub variable_name: Option<String>,
8}
9
10// Move selector configuration.
11#[derive(Debug, Clone, Deserialize, Serialize)]
12#[serde(tag = "type", rename_all = "snake_case")]
13pub enum MoveSelectorConfig {
14    // Change move selector (scalar variables).
15    ChangeMoveSelector(ChangeMoveConfig),
16
17    // Swap move selector (scalar variables).
18    SwapMoveSelector(SwapMoveConfig),
19
20    // Nearby change move selector (scalar variables).
21    NearbyChangeMoveSelector(NearbyChangeMoveConfig),
22
23    // Nearby swap move selector (scalar variables).
24    NearbySwapMoveSelector(NearbySwapMoveConfig),
25
26    // Pillar change move selector (scalar variables).
27    PillarChangeMoveSelector(PillarChangeMoveConfig),
28
29    // Pillar swap move selector (scalar variables).
30    PillarSwapMoveSelector(PillarSwapMoveConfig),
31
32    // Ruin-and-recreate move selector (scalar variables).
33    RuinRecreateMoveSelector(RuinRecreateMoveSelectorConfig),
34
35    // Atomic grouped scalar move selector.
36    GroupedScalarMoveSelector(GroupedScalarMoveSelectorConfig),
37
38    // List change move selector — relocates single elements within/between routes.
39    ListChangeMoveSelector(ListChangeMoveConfig),
40
41    // Nearby list change move selector — distance-pruned element relocation.
42    NearbyListChangeMoveSelector(NearbyListChangeMoveConfig),
43
44    // List swap move selector — swaps single elements within/between routes.
45    ListSwapMoveSelector(ListSwapMoveConfig),
46
47    // List permute move selector — permutes contiguous windows within routes.
48    ListPermuteMoveSelector(ListPermuteMoveConfig),
49
50    // List precedence move selector — prioritizes critical list arcs in precedence makespan models.
51    ListPrecedenceMoveSelector(ListPrecedenceMoveConfig),
52
53    // Nearby list swap move selector — distance-pruned element swap.
54    NearbyListSwapMoveSelector(NearbyListSwapMoveConfig),
55
56    // Sublist change move selector (Or-opt) — relocates contiguous segments.
57    SublistChangeMoveSelector(SublistChangeMoveConfig),
58
59    // Sublist swap move selector — swaps contiguous segments between routes.
60    SublistSwapMoveSelector(SublistSwapMoveConfig),
61
62    // List reverse move selector (2-opt) — reverses segments within a route.
63    ListReverseMoveSelector(ListReverseMoveConfig),
64
65    // K-opt move selector — generalised route reconnection.
66    KOptMoveSelector(KOptMoveSelectorConfig),
67
68    // List ruin move selector (LNS) — removes elements for reinsertion.
69    ListRuinMoveSelector(ListRuinMoveSelectorConfig),
70
71    // Neighborhood that limits the number of yielded candidates from a child selector while
72    // preserving selector order.
73    LimitedNeighborhood(LimitedNeighborhoodConfig),
74
75    // Union of multiple selectors.
76    UnionMoveSelector(UnionMoveSelectorConfig),
77
78    // Cartesian product of selectors. Evaluates the right child on the left preview state,
79    // composes tabu ids in selector order, and rejects left children that require full score
80    // evaluation during preview.
81    CartesianProductMoveSelector(CartesianProductConfig),
82
83    // Conflict-directed scalar repair selector.
84    ConflictRepairMoveSelector(ConflictRepairMoveSelectorConfig),
85
86    // Conflict-directed compound scalar repair selector with framework-enforced hard improvement.
87    CompoundConflictRepairMoveSelector(CompoundConflictRepairMoveSelectorConfig),
88}
89
90// Change move configuration.
91#[derive(Debug, Clone, Default, Deserialize, Serialize)]
92#[serde(rename_all = "snake_case")]
93pub struct ChangeMoveConfig {
94    pub value_candidate_limit: Option<usize>,
95    #[serde(flatten)]
96    pub target: VariableTargetConfig,
97}
98
99// Swap move configuration.
100#[derive(Debug, Clone, Default, Deserialize, Serialize)]
101#[serde(rename_all = "snake_case")]
102pub struct SwapMoveConfig {
103    #[serde(flatten)]
104    pub target: VariableTargetConfig,
105}
106
107// Nearby change move configuration.
108#[derive(Debug, Clone, Deserialize, Serialize)]
109#[serde(rename_all = "snake_case")]
110pub struct NearbyChangeMoveConfig {
111    pub max_nearby: usize,
112    pub value_candidate_limit: Option<usize>,
113    #[serde(flatten)]
114    pub target: VariableTargetConfig,
115}
116
117impl Default for NearbyChangeMoveConfig {
118    fn default() -> Self {
119        let (max_nearby, target) = default_nearby_target_config();
120        Self {
121            max_nearby,
122            value_candidate_limit: None,
123            target,
124        }
125    }
126}
127
128// Nearby swap move configuration.
129#[derive(Debug, Clone, Deserialize, Serialize)]
130#[serde(rename_all = "snake_case")]
131pub struct NearbySwapMoveConfig {
132    pub max_nearby: usize,
133    #[serde(flatten)]
134    pub target: VariableTargetConfig,
135}
136
137impl Default for NearbySwapMoveConfig {
138    fn default() -> Self {
139        let (max_nearby, target) = default_nearby_target_config();
140        Self { max_nearby, target }
141    }
142}
143
144// Pillar change move configuration.
145#[derive(Debug, Clone, Default, Deserialize, Serialize)]
146#[serde(rename_all = "snake_case")]
147pub struct PillarChangeMoveConfig {
148    pub minimum_sub_pillar_size: usize,
149    pub maximum_sub_pillar_size: usize,
150    pub value_candidate_limit: Option<usize>,
151    #[serde(flatten)]
152    pub target: VariableTargetConfig,
153}
154
155// Pillar swap move configuration.
156#[derive(Debug, Clone, Default, Deserialize, Serialize)]
157#[serde(rename_all = "snake_case")]
158pub struct PillarSwapMoveConfig {
159    pub minimum_sub_pillar_size: usize,
160    pub maximum_sub_pillar_size: usize,
161    #[serde(flatten)]
162    pub target: VariableTargetConfig,
163}
164
165#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
166#[serde(rename_all = "snake_case")]
167pub enum RecreateHeuristicType {
168    #[default]
169    FirstFit,
170    CheapestInsertion,
171}
172
173#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
174#[serde(rename_all = "snake_case")]
175pub enum UnionSelectionOrder {
176    #[default]
177    Sequential,
178    RoundRobin,
179    RotatingRoundRobin,
180    StratifiedRandom,
181}
182
183// Ruin-and-recreate move configuration.
184#[derive(Debug, Clone, Deserialize, Serialize)]
185#[serde(rename_all = "snake_case")]
186pub struct RuinRecreateMoveSelectorConfig {
187    pub min_ruin_count: usize,
188    pub max_ruin_count: usize,
189    pub moves_per_step: Option<usize>,
190    pub value_candidate_limit: Option<usize>,
191    pub recreate_heuristic_type: RecreateHeuristicType,
192    #[serde(flatten)]
193    pub target: VariableTargetConfig,
194}
195
196impl Default for RuinRecreateMoveSelectorConfig {
197    fn default() -> Self {
198        Self {
199            min_ruin_count: 2,
200            max_ruin_count: 5,
201            moves_per_step: None,
202            value_candidate_limit: None,
203            recreate_heuristic_type: RecreateHeuristicType::FirstFit,
204            target: VariableTargetConfig::default(),
205        }
206    }
207}
208
209// Configuration for `GroupedScalarMoveSelector`.
210#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
211#[serde(rename_all = "snake_case")]
212pub struct GroupedScalarMoveSelectorConfig {
213    pub group_name: String,
214    pub value_candidate_limit: Option<usize>,
215    pub max_moves_per_step: Option<usize>,
216    #[serde(default)]
217    pub require_hard_improvement: bool,
218}
219
220// Configuration for `ListChangeMoveSelector`.
221#[derive(Debug, Clone, Default, Deserialize, Serialize)]
222#[serde(rename_all = "snake_case")]
223pub struct ListChangeMoveConfig {
224    #[serde(flatten)]
225    pub target: VariableTargetConfig,
226}
227
228// Configuration for `NearbyListChangeMoveSelector`.
229#[derive(Debug, Clone, Deserialize, Serialize)]
230#[serde(rename_all = "snake_case")]
231pub struct NearbyListChangeMoveConfig {
232    // Maximum nearby destination positions to consider per source element.
233    pub max_nearby: usize,
234    #[serde(flatten)]
235    pub target: VariableTargetConfig,
236}
237
238impl Default for NearbyListChangeMoveConfig {
239    fn default() -> Self {
240        let (max_nearby, target) = default_nearby_target_config();
241        Self { max_nearby, target }
242    }
243}
244
245// Configuration for `ListSwapMoveSelector`.
246#[derive(Debug, Clone, Default, Deserialize, Serialize)]
247#[serde(rename_all = "snake_case")]
248pub struct ListSwapMoveConfig {
249    #[serde(flatten)]
250    pub target: VariableTargetConfig,
251}
252
253// Configuration for `ListPermuteMoveSelector`.
254#[derive(Debug, Clone, Deserialize, Serialize)]
255#[serde(rename_all = "snake_case")]
256pub struct ListPermuteMoveConfig {
257    // Minimum window size (inclusive). Default: 2.
258    pub min_window_size: usize,
259    // Maximum window size (inclusive). Default: 5.
260    pub max_window_size: usize,
261    #[serde(flatten)]
262    pub target: VariableTargetConfig,
263}
264
265impl Default for ListPermuteMoveConfig {
266    fn default() -> Self {
267        Self {
268            min_window_size: 2,
269            max_window_size: 5,
270            target: VariableTargetConfig::default(),
271        }
272    }
273}
274
275// Configuration for `ListPrecedenceMoveSelector`.
276#[derive(Debug, Clone, Default, Deserialize, Serialize)]
277#[serde(rename_all = "snake_case")]
278pub struct ListPrecedenceMoveConfig {
279    #[serde(flatten)]
280    pub target: VariableTargetConfig,
281}
282
283// Configuration for `NearbyListSwapMoveSelector`.
284#[derive(Debug, Clone, Deserialize, Serialize)]
285#[serde(rename_all = "snake_case")]
286pub struct NearbyListSwapMoveConfig {
287    // Maximum nearby swap partners to consider per source element.
288    pub max_nearby: usize,
289    #[serde(flatten)]
290    pub target: VariableTargetConfig,
291}
292
293impl Default for NearbyListSwapMoveConfig {
294    fn default() -> Self {
295        let (max_nearby, target) = default_nearby_target_config();
296        Self { max_nearby, target }
297    }
298}
299
300// Configuration for `SublistChangeMoveSelector` (Or-opt).
301#[derive(Debug, Clone, Deserialize, Serialize)]
302#[serde(rename_all = "snake_case")]
303pub struct SublistChangeMoveConfig {
304    // Minimum segment size (inclusive). Default: 1.
305    pub min_sublist_size: usize,
306    // Maximum segment size (inclusive). Default: 3.
307    pub max_sublist_size: usize,
308    #[serde(flatten)]
309    pub target: VariableTargetConfig,
310}
311
312impl Default for SublistChangeMoveConfig {
313    fn default() -> Self {
314        let (min_sublist_size, max_sublist_size, target) = default_sublist_target_config();
315        Self {
316            min_sublist_size,
317            max_sublist_size,
318            target,
319        }
320    }
321}
322
323// Configuration for `SublistSwapMoveSelector`.
324#[derive(Debug, Clone, Deserialize, Serialize)]
325#[serde(rename_all = "snake_case")]
326pub struct SublistSwapMoveConfig {
327    // Minimum segment size (inclusive). Default: 1.
328    pub min_sublist_size: usize,
329    // Maximum segment size (inclusive). Default: 3.
330    pub max_sublist_size: usize,
331    #[serde(flatten)]
332    pub target: VariableTargetConfig,
333}
334
335impl Default for SublistSwapMoveConfig {
336    fn default() -> Self {
337        let (min_sublist_size, max_sublist_size, target) = default_sublist_target_config();
338        Self {
339            min_sublist_size,
340            max_sublist_size,
341            target,
342        }
343    }
344}
345
346// Configuration for `ListReverseMoveSelector` (2-opt).
347#[derive(Debug, Clone, Default, Deserialize, Serialize)]
348#[serde(rename_all = "snake_case")]
349pub struct ListReverseMoveConfig {
350    #[serde(flatten)]
351    pub target: VariableTargetConfig,
352}
353
354// Configuration for `KOptMoveSelector`.
355#[derive(Debug, Clone, Deserialize, Serialize)]
356#[serde(rename_all = "snake_case")]
357pub struct KOptMoveSelectorConfig {
358    // K value (number of cuts). Default: 3.
359    pub k: usize,
360    // Minimum segment length between cuts. Default: 1.
361    pub min_segment_len: usize,
362    // Maximum nearby positions to consider per cut. Default: 0 (full enumeration).
363    // When > 0, uses distance-pruned NearbyKOptMoveSelector instead of full KOptMoveSelector.
364    pub max_nearby: usize,
365    #[serde(flatten)]
366    pub target: VariableTargetConfig,
367}
368
369impl Default for KOptMoveSelectorConfig {
370    fn default() -> Self {
371        Self {
372            k: 3,
373            min_segment_len: 1,
374            max_nearby: 0,
375            target: VariableTargetConfig::default(),
376        }
377    }
378}
379
380// Configuration for `ListRuinMoveSelector` (LNS).
381#[derive(Debug, Clone, Deserialize, Serialize)]
382#[serde(rename_all = "snake_case")]
383pub struct ListRuinMoveSelectorConfig {
384    // Minimum number of elements to ruin per move. Default: 2.
385    pub min_ruin_count: usize,
386    // Maximum number of elements to ruin per move. Default: 5.
387    pub max_ruin_count: usize,
388    // Number of ruin moves to generate per step. Default: 10.
389    pub moves_per_step: Option<usize>,
390    // Optional maximum source list length eligible for this selector.
391    pub max_source_list_len: Option<usize>,
392    // Whether recreate should skip currently empty destination lists.
393    #[serde(default)]
394    pub skip_empty_destinations: bool,
395    #[serde(flatten)]
396    pub target: VariableTargetConfig,
397}
398
399impl Default for ListRuinMoveSelectorConfig {
400    fn default() -> Self {
401        Self {
402            min_ruin_count: 2,
403            max_ruin_count: 5,
404            moves_per_step: None,
405            max_source_list_len: None,
406            skip_empty_destinations: false,
407            target: VariableTargetConfig::default(),
408        }
409    }
410}
411
412// Configuration for `LimitedNeighborhood`.
413#[derive(Debug, Clone, Deserialize, Serialize)]
414#[serde(rename_all = "snake_case")]
415pub struct LimitedNeighborhoodConfig {
416    // Maximum number of moves yielded from the child selector.
417    pub selected_count_limit: usize,
418    // Child selector to wrap.
419    pub selector: Box<MoveSelectorConfig>,
420}
421
422// Union move selector configuration.
423#[derive(Debug, Clone, Default, Deserialize, Serialize)]
424#[serde(rename_all = "snake_case")]
425pub struct UnionMoveSelectorConfig {
426    #[serde(default)]
427    pub selection_order: UnionSelectionOrder,
428    // Child selectors.
429    pub selectors: Vec<MoveSelectorConfig>,
430}
431
432// Cartesian product move selector configuration.
433#[derive(Debug, Clone, Default, Deserialize, Serialize)]
434#[serde(rename_all = "snake_case")]
435pub struct CartesianProductConfig {
436    // When true, search phases reject composed candidates unless the hard score improves.
437    #[serde(default)]
438    pub require_hard_improvement: bool,
439
440    // Child selectors.
441    pub selectors: Vec<MoveSelectorConfig>,
442}
443
444#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
445#[serde(rename_all = "snake_case")]
446pub struct ConflictRepairMoveSelectorConfig {
447    pub constraints: Vec<String>,
448    #[serde(default = "default_conflict_repair_max_matches")]
449    pub max_matches_per_step: usize,
450    #[serde(default = "default_conflict_repair_max_repairs")]
451    pub max_repairs_per_match: usize,
452    #[serde(default = "default_conflict_repair_max_moves")]
453    pub max_moves_per_step: usize,
454    #[serde(default)]
455    pub require_hard_improvement: bool,
456    #[serde(default)]
457    pub include_soft_matches: bool,
458}
459
460impl Default for ConflictRepairMoveSelectorConfig {
461    fn default() -> Self {
462        Self {
463            constraints: Vec::new(),
464            max_matches_per_step: default_conflict_repair_max_matches(),
465            max_repairs_per_match: default_conflict_repair_max_repairs(),
466            max_moves_per_step: default_conflict_repair_max_moves(),
467            require_hard_improvement: false,
468            include_soft_matches: false,
469        }
470    }
471}
472
473#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
474#[serde(rename_all = "snake_case")]
475pub struct CompoundConflictRepairMoveSelectorConfig {
476    pub constraints: Vec<String>,
477    #[serde(default = "default_conflict_repair_max_matches")]
478    pub max_matches_per_step: usize,
479    #[serde(default = "default_conflict_repair_max_repairs")]
480    pub max_repairs_per_match: usize,
481    #[serde(default = "default_conflict_repair_max_moves")]
482    pub max_moves_per_step: usize,
483    #[serde(default = "default_require_hard_improvement")]
484    pub require_hard_improvement: bool,
485    #[serde(default)]
486    pub include_soft_matches: bool,
487}
488
489impl Default for CompoundConflictRepairMoveSelectorConfig {
490    fn default() -> Self {
491        Self {
492            constraints: Vec::new(),
493            max_matches_per_step: default_conflict_repair_max_matches(),
494            max_repairs_per_match: default_conflict_repair_max_repairs(),
495            max_moves_per_step: default_conflict_repair_max_moves(),
496            require_hard_improvement: default_require_hard_improvement(),
497            include_soft_matches: false,
498        }
499    }
500}
501
502fn default_conflict_repair_max_matches() -> usize {
503    16
504}
505
506fn default_conflict_repair_max_repairs() -> usize {
507    32
508}
509
510fn default_conflict_repair_max_moves() -> usize {
511    256
512}
513
514fn default_require_hard_improvement() -> bool {
515    true
516}
517
518fn default_nearby_target_config() -> (usize, VariableTargetConfig) {
519    (10, VariableTargetConfig::default())
520}
521
522fn default_sublist_target_config() -> (usize, usize, VariableTargetConfig) {
523    (1, 3, VariableTargetConfig::default())
524}