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