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