1use std::path::Path;
44use std::time::Duration;
45
46use serde::{Deserialize, Serialize};
47use thiserror::Error;
48
49#[derive(Debug, Error)]
51pub enum ConfigError {
52 #[error("IO error: {0}")]
53 Io(#[from] std::io::Error),
54
55 #[error("TOML parse error: {0}")]
56 Toml(#[from] toml::de::Error),
57
58 #[error("YAML parse error: {0}")]
59 Yaml(#[from] serde_yaml::Error),
60
61 #[error("Invalid configuration: {0}")]
62 Invalid(String),
63}
64
65#[derive(Debug, Clone, Default, Deserialize, Serialize)]
67#[serde(rename_all = "snake_case")]
68pub struct SolverConfig {
69 #[serde(default)]
71 pub environment_mode: EnvironmentMode,
72
73 #[serde(default)]
75 pub random_seed: Option<u64>,
76
77 #[serde(default)]
79 pub move_thread_count: MoveThreadCount,
80
81 #[serde(default)]
83 pub termination: Option<TerminationConfig>,
84
85 #[serde(default)]
87 pub score_director: Option<ScoreDirectorConfig>,
88
89 #[serde(default)]
91 pub phases: Vec<PhaseConfig>,
92}
93
94impl SolverConfig {
95 pub fn new() -> Self {
97 Self::default()
98 }
99
100 pub fn load(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
106 Self::from_toml_file(path)
107 }
108
109 pub fn from_toml_file(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
111 let contents = std::fs::read_to_string(path)?;
112 Self::from_toml_str(&contents)
113 }
114
115 pub fn from_toml_str(s: &str) -> Result<Self, ConfigError> {
117 Ok(toml::from_str(s)?)
118 }
119
120 pub fn from_yaml_file(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
122 let contents = std::fs::read_to_string(path)?;
123 Self::from_yaml_str(&contents)
124 }
125
126 pub fn from_yaml_str(s: &str) -> Result<Self, ConfigError> {
128 Ok(serde_yaml::from_str(s)?)
129 }
130
131 pub fn with_termination_seconds(mut self, seconds: u64) -> Self {
133 self.termination = Some(TerminationConfig {
134 seconds_spent_limit: Some(seconds),
135 ..self.termination.unwrap_or_default()
136 });
137 self
138 }
139
140 pub fn with_random_seed(mut self, seed: u64) -> Self {
142 self.random_seed = Some(seed);
143 self
144 }
145
146 pub fn with_phase(mut self, phase: PhaseConfig) -> Self {
148 self.phases.push(phase);
149 self
150 }
151
152 pub fn time_limit(&self) -> Option<Duration> {
170 self.termination.as_ref().and_then(|t| t.time_limit())
171 }
172}
173
174#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
176#[serde(rename_all = "snake_case")]
177pub enum EnvironmentMode {
178 #[default]
180 NonReproducible,
181
182 Reproducible,
184
185 FastAssert,
187
188 FullAssert,
190}
191
192#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
194#[serde(rename_all = "snake_case")]
195pub enum MoveThreadCount {
196 #[default]
198 Auto,
199
200 None,
202
203 Count(usize),
205}
206
207#[derive(Debug, Clone, Default, Deserialize, Serialize)]
209#[serde(rename_all = "snake_case")]
210pub struct TerminationConfig {
211 pub seconds_spent_limit: Option<u64>,
213
214 pub minutes_spent_limit: Option<u64>,
216
217 pub best_score_limit: Option<String>,
219
220 pub step_count_limit: Option<u64>,
222
223 pub unimproved_step_count_limit: Option<u64>,
225
226 pub unimproved_seconds_spent_limit: Option<u64>,
228}
229
230impl TerminationConfig {
231 pub fn time_limit(&self) -> Option<Duration> {
233 let seconds =
234 self.seconds_spent_limit.unwrap_or(0) + self.minutes_spent_limit.unwrap_or(0) * 60;
235 if seconds > 0 {
236 Some(Duration::from_secs(seconds))
237 } else {
238 None
239 }
240 }
241
242 pub fn unimproved_time_limit(&self) -> Option<Duration> {
244 self.unimproved_seconds_spent_limit.map(Duration::from_secs)
245 }
246}
247
248#[derive(Debug, Clone, Default, Deserialize, Serialize)]
250#[serde(rename_all = "snake_case")]
251pub struct ScoreDirectorConfig {
252 pub constraint_provider: Option<String>,
254
255 #[serde(default)]
257 pub constraint_match_enabled: bool,
258}
259
260#[derive(Debug, Clone, Deserialize, Serialize)]
262#[serde(tag = "type", rename_all = "snake_case")]
263pub enum PhaseConfig {
264 ConstructionHeuristic(ConstructionHeuristicConfig),
266
267 LocalSearch(LocalSearchConfig),
269
270 ExhaustiveSearch(ExhaustiveSearchConfig),
272
273 PartitionedSearch(PartitionedSearchConfig),
275
276 Custom(CustomPhaseConfig),
278}
279
280#[derive(Debug, Clone, Default, Deserialize, Serialize)]
282#[serde(rename_all = "snake_case")]
283pub struct ConstructionHeuristicConfig {
284 #[serde(default)]
286 pub construction_heuristic_type: ConstructionHeuristicType,
287
288 pub termination: Option<TerminationConfig>,
290}
291
292#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
294#[serde(rename_all = "snake_case")]
295pub enum ConstructionHeuristicType {
296 #[default]
298 FirstFit,
299
300 FirstFitDecreasing,
302
303 WeakestFit,
305
306 WeakestFitDecreasing,
308
309 StrongestFit,
311
312 StrongestFitDecreasing,
314
315 CheapestInsertion,
317
318 AllocateEntityFromQueue,
320
321 AllocateToValueFromQueue,
323
324 ListRoundRobin,
326
327 ListCheapestInsertion,
329
330 ListRegretInsertion,
332}
333
334#[derive(Debug, Clone, Default, Deserialize, Serialize)]
336#[serde(rename_all = "snake_case")]
337pub struct LocalSearchConfig {
338 pub acceptor: Option<AcceptorConfig>,
340
341 pub forager: Option<ForagerConfig>,
343
344 pub move_selector: Option<MoveSelectorConfig>,
346
347 pub termination: Option<TerminationConfig>,
349}
350
351#[derive(Debug, Clone, Deserialize, Serialize)]
353#[serde(tag = "type", rename_all = "snake_case")]
354pub enum AcceptorConfig {
355 HillClimbing,
357
358 TabuSearch(TabuSearchConfig),
360
361 SimulatedAnnealing(SimulatedAnnealingConfig),
363
364 LateAcceptance(LateAcceptanceConfig),
366
367 GreatDeluge(GreatDelugeConfig),
369}
370
371#[derive(Debug, Clone, Default, Deserialize, Serialize)]
373#[serde(rename_all = "snake_case")]
374pub struct TabuSearchConfig {
375 pub entity_tabu_size: Option<usize>,
377
378 pub value_tabu_size: Option<usize>,
380
381 pub move_tabu_size: Option<usize>,
383
384 pub undo_move_tabu_size: Option<usize>,
386}
387
388#[derive(Debug, Clone, Default, Deserialize, Serialize)]
390#[serde(rename_all = "snake_case")]
391pub struct SimulatedAnnealingConfig {
392 pub starting_temperature: Option<String>,
394}
395
396#[derive(Debug, Clone, Default, Deserialize, Serialize)]
398#[serde(rename_all = "snake_case")]
399pub struct LateAcceptanceConfig {
400 pub late_acceptance_size: Option<usize>,
402}
403
404#[derive(Debug, Clone, Default, Deserialize, Serialize)]
406#[serde(rename_all = "snake_case")]
407pub struct GreatDelugeConfig {
408 pub water_level_increase_ratio: Option<f64>,
410}
411
412#[derive(Debug, Clone, Default, Deserialize, Serialize)]
414#[serde(rename_all = "snake_case")]
415pub struct ForagerConfig {
416 pub accepted_count_limit: Option<usize>,
418
419 pub pick_early_type: Option<PickEarlyType>,
421}
422
423#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
425#[serde(rename_all = "snake_case")]
426pub enum PickEarlyType {
427 #[default]
429 Never,
430
431 FirstBestScoreImproving,
433
434 FirstLastStepScoreImproving,
436}
437
438#[derive(Debug, Clone, Deserialize, Serialize)]
440#[serde(tag = "type", rename_all = "snake_case")]
441pub enum MoveSelectorConfig {
442 ChangeMoveSelector(ChangeMoveConfig),
444
445 SwapMoveSelector(SwapMoveConfig),
447
448 ListChangeMoveSelector(ListChangeMoveConfig),
450
451 NearbyListChangeMoveSelector(NearbyListChangeMoveConfig),
453
454 ListSwapMoveSelector(ListSwapMoveConfig),
456
457 NearbyListSwapMoveSelector(NearbyListSwapMoveConfig),
459
460 SubListChangeMoveSelector(SubListChangeMoveConfig),
462
463 SubListSwapMoveSelector(SubListSwapMoveConfig),
465
466 ListReverseMoveSelector(ListReverseMoveConfig),
468
469 KOptMoveSelector(KOptMoveSelectorConfig),
471
472 ListRuinMoveSelector(ListRuinMoveSelectorConfig),
474
475 UnionMoveSelector(UnionMoveSelectorConfig),
477
478 CartesianProductMoveSelector(CartesianProductConfig),
480}
481
482#[derive(Debug, Clone, Default, Deserialize, Serialize)]
484#[serde(rename_all = "snake_case")]
485pub struct ListChangeMoveConfig {
486 pub variable_name: Option<String>,
488}
489
490#[derive(Debug, Clone, Deserialize, Serialize)]
492#[serde(rename_all = "snake_case")]
493pub struct NearbyListChangeMoveConfig {
494 pub max_nearby: usize,
496 pub variable_name: Option<String>,
498}
499
500impl Default for NearbyListChangeMoveConfig {
501 fn default() -> Self {
502 Self {
503 max_nearby: 10,
504 variable_name: None,
505 }
506 }
507}
508
509#[derive(Debug, Clone, Default, Deserialize, Serialize)]
511#[serde(rename_all = "snake_case")]
512pub struct ListSwapMoveConfig {
513 pub variable_name: Option<String>,
515}
516
517#[derive(Debug, Clone, Deserialize, Serialize)]
519#[serde(rename_all = "snake_case")]
520pub struct NearbyListSwapMoveConfig {
521 pub max_nearby: usize,
523 pub variable_name: Option<String>,
525}
526
527impl Default for NearbyListSwapMoveConfig {
528 fn default() -> Self {
529 Self {
530 max_nearby: 10,
531 variable_name: None,
532 }
533 }
534}
535
536#[derive(Debug, Clone, Deserialize, Serialize)]
538#[serde(rename_all = "snake_case")]
539pub struct SubListChangeMoveConfig {
540 pub min_sublist_size: usize,
542 pub max_sublist_size: usize,
544 pub variable_name: Option<String>,
546}
547
548impl Default for SubListChangeMoveConfig {
549 fn default() -> Self {
550 Self {
551 min_sublist_size: 1,
552 max_sublist_size: 3,
553 variable_name: None,
554 }
555 }
556}
557
558#[derive(Debug, Clone, Deserialize, Serialize)]
560#[serde(rename_all = "snake_case")]
561pub struct SubListSwapMoveConfig {
562 pub min_sublist_size: usize,
564 pub max_sublist_size: usize,
566 pub variable_name: Option<String>,
568}
569
570impl Default for SubListSwapMoveConfig {
571 fn default() -> Self {
572 Self {
573 min_sublist_size: 1,
574 max_sublist_size: 3,
575 variable_name: None,
576 }
577 }
578}
579
580#[derive(Debug, Clone, Default, Deserialize, Serialize)]
582#[serde(rename_all = "snake_case")]
583pub struct ListReverseMoveConfig {
584 pub variable_name: Option<String>,
586}
587
588#[derive(Debug, Clone, Deserialize, Serialize)]
590#[serde(rename_all = "snake_case")]
591pub struct KOptMoveSelectorConfig {
592 pub k: usize,
594 pub min_segment_len: usize,
596 pub variable_name: Option<String>,
598}
599
600impl Default for KOptMoveSelectorConfig {
601 fn default() -> Self {
602 Self {
603 k: 3,
604 min_segment_len: 1,
605 variable_name: None,
606 }
607 }
608}
609
610#[derive(Debug, Clone, Deserialize, Serialize)]
612#[serde(rename_all = "snake_case")]
613pub struct ListRuinMoveSelectorConfig {
614 pub min_ruin_count: usize,
616 pub max_ruin_count: usize,
618 pub moves_per_step: Option<usize>,
620 pub variable_name: Option<String>,
622}
623
624impl Default for ListRuinMoveSelectorConfig {
625 fn default() -> Self {
626 Self {
627 min_ruin_count: 2,
628 max_ruin_count: 5,
629 moves_per_step: None,
630 variable_name: None,
631 }
632 }
633}
634
635#[derive(Debug, Clone, Default, Deserialize, Serialize)]
637#[serde(rename_all = "snake_case")]
638pub struct ChangeMoveConfig {
639 pub entity_class: Option<String>,
641}
642
643#[derive(Debug, Clone, Default, Deserialize, Serialize)]
645#[serde(rename_all = "snake_case")]
646pub struct SwapMoveConfig {
647 pub entity_class: Option<String>,
649}
650
651#[derive(Debug, Clone, Default, Deserialize, Serialize)]
653#[serde(rename_all = "snake_case")]
654pub struct UnionMoveSelectorConfig {
655 pub selectors: Vec<MoveSelectorConfig>,
657}
658
659#[derive(Debug, Clone, Default, Deserialize, Serialize)]
661#[serde(rename_all = "snake_case")]
662pub struct CartesianProductConfig {
663 pub selectors: Vec<MoveSelectorConfig>,
665}
666
667#[derive(Debug, Clone, Default, Deserialize, Serialize)]
669#[serde(rename_all = "snake_case")]
670pub struct ExhaustiveSearchConfig {
671 #[serde(default)]
673 pub exhaustive_search_type: ExhaustiveSearchType,
674
675 pub termination: Option<TerminationConfig>,
677}
678
679#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
681#[serde(rename_all = "snake_case")]
682pub enum ExhaustiveSearchType {
683 #[default]
685 BranchAndBound,
686
687 BruteForce,
689}
690
691#[derive(Debug, Clone, Default, Deserialize, Serialize)]
693#[serde(rename_all = "snake_case")]
694pub struct PartitionedSearchConfig {
695 pub partition_count: Option<usize>,
697
698 pub termination: Option<TerminationConfig>,
700}
701
702#[derive(Debug, Clone, Default, Deserialize, Serialize)]
704#[serde(rename_all = "snake_case")]
705pub struct CustomPhaseConfig {
706 pub custom_phase_class: Option<String>,
708}
709
710#[derive(Debug, Clone, Default)]
712pub struct SolverConfigOverride {
713 pub termination: Option<TerminationConfig>,
715}
716
717impl SolverConfigOverride {
718 pub fn with_termination(termination: TerminationConfig) -> Self {
720 SolverConfigOverride {
721 termination: Some(termination),
722 }
723 }
724}
725
726#[cfg(test)]
727mod tests;