solverforge_solver/heuristic/selector/scalar_neighborhood/
spec.rs1use std::fmt;
2
3use solverforge_config::{MoveSelectorConfig, RecreateHeuristicType};
4use solverforge_core::domain::PlanningSolution;
5
6use crate::builder::{RuntimeScalarSlot, RuntimeScalarSlotId, ScalarAccessCapability};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum ScalarNeighborhoodKind {
15 Change,
16 Swap,
17 NearbyChange,
18 NearbySwap,
19 PillarChange,
20 PillarSwap,
21 RuinRecreate,
22}
23
24impl ScalarNeighborhoodKind {
25 pub(crate) fn required_capability(self) -> Option<ScalarAccessCapability> {
26 match self {
27 Self::Change | Self::PillarChange | Self::PillarSwap | Self::RuinRecreate => {
28 Some(ScalarAccessCapability::Candidates)
29 }
30 Self::NearbyChange => Some(ScalarAccessCapability::NearbyValue),
31 Self::NearbySwap => Some(ScalarAccessCapability::NearbyEntity),
32 Self::Swap => None,
33 }
34 }
35
36 pub(crate) fn selector_name(self) -> &'static str {
37 match self {
38 Self::Change => "change_move_selector",
39 Self::Swap => "swap_move_selector",
40 Self::NearbyChange => "nearby_change_move_selector",
41 Self::NearbySwap => "nearby_swap_move_selector",
42 Self::PillarChange => "pillar_change_move_selector",
43 Self::PillarSwap => "pillar_swap_move_selector",
44 Self::RuinRecreate => "ruin_recreate_move_selector",
45 }
46 }
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub(crate) enum ScalarNeighborhoodSpec {
53 Change {
54 value_candidate_limit: Option<usize>,
55 },
56 Swap,
57 NearbyChange {
58 max_nearby: usize,
59 value_candidate_limit: Option<usize>,
60 },
61 NearbySwap {
62 max_nearby: usize,
63 },
64 PillarChange {
65 minimum_sub_pillar_size: usize,
66 maximum_sub_pillar_size: usize,
67 value_candidate_limit: Option<usize>,
68 },
69 PillarSwap {
70 minimum_sub_pillar_size: usize,
71 maximum_sub_pillar_size: usize,
72 },
73 RuinRecreate {
74 min_ruin_count: usize,
75 max_ruin_count: usize,
76 moves_per_step: usize,
77 value_candidate_limit: Option<usize>,
78 recreate_heuristic_type: RecreateHeuristicType,
79 },
80}
81
82impl ScalarNeighborhoodSpec {
83 pub(crate) fn from_config(
84 kind: ScalarNeighborhoodKind,
85 config: &MoveSelectorConfig,
86 ) -> Result<Self, ScalarNeighborhoodBindingError> {
87 let spec = match (kind, config) {
88 (ScalarNeighborhoodKind::Change, MoveSelectorConfig::ChangeMoveSelector(config)) => {
89 Self::Change {
90 value_candidate_limit: config.value_candidate_limit,
91 }
92 }
93 (ScalarNeighborhoodKind::Swap, MoveSelectorConfig::SwapMoveSelector(_)) => Self::Swap,
94 (
95 ScalarNeighborhoodKind::NearbyChange,
96 MoveSelectorConfig::NearbyChangeMoveSelector(config),
97 ) => Self::NearbyChange {
98 max_nearby: config.max_nearby,
99 value_candidate_limit: config.value_candidate_limit,
100 },
101 (
102 ScalarNeighborhoodKind::NearbySwap,
103 MoveSelectorConfig::NearbySwapMoveSelector(config),
104 ) => Self::NearbySwap {
105 max_nearby: config.max_nearby,
106 },
107 (
108 ScalarNeighborhoodKind::PillarChange,
109 MoveSelectorConfig::PillarChangeMoveSelector(config),
110 ) => Self::PillarChange {
111 minimum_sub_pillar_size: config.minimum_sub_pillar_size,
112 maximum_sub_pillar_size: config.maximum_sub_pillar_size,
113 value_candidate_limit: config.value_candidate_limit,
114 },
115 (
116 ScalarNeighborhoodKind::PillarSwap,
117 MoveSelectorConfig::PillarSwapMoveSelector(config),
118 ) => Self::PillarSwap {
119 minimum_sub_pillar_size: config.minimum_sub_pillar_size,
120 maximum_sub_pillar_size: config.maximum_sub_pillar_size,
121 },
122 (
123 ScalarNeighborhoodKind::RuinRecreate,
124 MoveSelectorConfig::RuinRecreateMoveSelector(config),
125 ) => {
126 if config.min_ruin_count == 0 || config.max_ruin_count < config.min_ruin_count {
127 return Err(ScalarNeighborhoodBindingError::InvalidRuinBounds {
128 min_ruin_count: config.min_ruin_count,
129 max_ruin_count: config.max_ruin_count,
130 });
131 }
132 Self::RuinRecreate {
133 min_ruin_count: config.min_ruin_count,
134 max_ruin_count: config.max_ruin_count,
135 moves_per_step: config.moves_per_step.unwrap_or(10).max(1),
136 value_candidate_limit: config.value_candidate_limit,
137 recreate_heuristic_type: config.recreate_heuristic_type,
138 }
139 }
140 _ => return Err(ScalarNeighborhoodBindingError::ConfigFamilyMismatch { kind }),
141 };
142 Ok(spec)
143 }
144
145 pub(crate) fn kind(self) -> ScalarNeighborhoodKind {
146 match self {
147 Self::Change { .. } => ScalarNeighborhoodKind::Change,
148 Self::Swap => ScalarNeighborhoodKind::Swap,
149 Self::NearbyChange { .. } => ScalarNeighborhoodKind::NearbyChange,
150 Self::NearbySwap { .. } => ScalarNeighborhoodKind::NearbySwap,
151 Self::PillarChange { .. } => ScalarNeighborhoodKind::PillarChange,
152 Self::PillarSwap { .. } => ScalarNeighborhoodKind::PillarSwap,
153 Self::RuinRecreate { .. } => ScalarNeighborhoodKind::RuinRecreate,
154 }
155 }
156}
157
158#[derive(Clone, Debug)]
161pub(crate) enum RuntimeScalarRecipe<S> {
162 Change {
163 slot: RuntimeScalarSlot<S>,
164 entity_index: usize,
165 to_value: Option<usize>,
166 },
167 Swap {
168 slot: RuntimeScalarSlot<S>,
169 left_entity_index: usize,
170 right_entity_index: usize,
171 },
172 PillarChange {
173 slot: RuntimeScalarSlot<S>,
174 entity_indices: Vec<usize>,
175 to_value: Option<usize>,
176 },
177 PillarSwap {
178 slot: RuntimeScalarSlot<S>,
179 left_indices: Vec<usize>,
180 right_indices: Vec<usize>,
181 },
182 RuinRecreate {
183 slot: RuntimeScalarSlot<S>,
184 entity_indices: Vec<usize>,
185 value_candidate_limit: Option<usize>,
186 recreate_heuristic_type: RecreateHeuristicType,
187 },
188}
189
190#[derive(Clone, Debug, PartialEq, Eq)]
194pub enum ScalarNeighborhoodBindingError {
195 ConfigFamilyMismatch {
196 kind: ScalarNeighborhoodKind,
197 },
198 MissingCapability {
199 slot: RuntimeScalarSlotId,
200 capability: ScalarAccessCapability,
201 },
202 InvalidRuinBounds {
203 min_ruin_count: usize,
204 max_ruin_count: usize,
205 },
206}
207
208impl ScalarNeighborhoodBindingError {
209 pub(crate) fn validate_slot<S>(
210 kind: ScalarNeighborhoodKind,
211 slot: &RuntimeScalarSlot<S>,
212 ) -> Result<(), Self>
213 where
214 S: PlanningSolution,
215 {
216 let Some(capability) = kind.required_capability() else {
217 return Ok(());
218 };
219 if slot.has_capability(capability) {
220 Ok(())
221 } else {
222 Err(Self::MissingCapability {
223 slot: slot.id(),
224 capability,
225 })
226 }
227 }
228}
229
230impl fmt::Display for ScalarNeighborhoodBindingError {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 match self {
233 Self::ConfigFamilyMismatch { kind } => {
234 write!(
235 f,
236 "{} received another selector configuration family",
237 kind.selector_name()
238 )
239 }
240 Self::MissingCapability { slot, capability } => {
241 write!(
242 f,
243 "{slot} does not provide required {}",
244 capability_label(*capability)
245 )
246 }
247 Self::InvalidRuinBounds {
248 min_ruin_count,
249 max_ruin_count,
250 } => write!(
251 f,
252 "ruin_recreate bounds require 1 <= min <= max, got {min_ruin_count}..={max_ruin_count}"
253 ),
254 }
255 }
256}
257
258impl std::error::Error for ScalarNeighborhoodBindingError {}
259
260fn capability_label(capability: ScalarAccessCapability) -> &'static str {
261 match capability {
262 ScalarAccessCapability::Candidates => "scalar candidate source",
263 ScalarAccessCapability::NearbyValue => "nearby scalar value source",
264 ScalarAccessCapability::NearbyEntity => "nearby scalar entity source",
265 ScalarAccessCapability::ConstructionEntityOrder => "construction entity-order source",
266 ScalarAccessCapability::ConstructionValueOrder => "construction value-order source",
267 }
268}