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