Skip to main content

solverforge_solver/heuristic/move/
scalar_union.rs

1/* ScalarMoveUnion - a monomorphized union of the canonical scalar move family.
2
3This allows local search to combine scalar change, swap, pillar, and
4ruin-recreate moves without trait-object dispatch.
5*/
6
7use std::fmt::Debug;
8
9use solverforge_core::domain::PlanningSolution;
10use solverforge_scoring::Director;
11
12use crate::stats::CandidateTraceIdentity;
13
14use super::{
15    ChangeMove, CompoundScalarMove, ConflictRepairMove, Move, MoveTabuSignature, PillarChangeMove,
16    PillarSwapMove, RuinRecreateMove, RuntimeCompoundMove, SwapMove,
17};
18
19/// A monomorphized union of the canonical scalar move family.
20///
21/// Implements `Move<S>` by delegating to the inner variant.
22/// `Copy` when `V: Copy`, avoiding heap allocation in the move selector hot path.
23#[allow(clippy::large_enum_variant)]
24pub enum ScalarMoveUnion<S, V> {
25    Change(ChangeMove<S, V>),
26    Swap(SwapMove<S, V>),
27    PillarChange(PillarChangeMove<S, V>),
28    PillarSwap(PillarSwapMove<S, V>),
29    RuinRecreate(RuinRecreateMove<S>),
30    CompoundScalar(CompoundScalarMove<S>),
31    ConflictRepair(ConflictRepairMove<S>),
32    RuntimeCompound(RuntimeCompoundMove<S>),
33}
34
35pub enum ScalarMoveUnionUndo<S, V>
36where
37    S: PlanningSolution,
38    V: Clone + PartialEq + Send + Sync + Debug + 'static,
39{
40    Change(<ChangeMove<S, V> as Move<S>>::Undo),
41    Swap(<SwapMove<S, V> as Move<S>>::Undo),
42    PillarChange(<PillarChangeMove<S, V> as Move<S>>::Undo),
43    PillarSwap(<PillarSwapMove<S, V> as Move<S>>::Undo),
44    RuinRecreate(<RuinRecreateMove<S> as Move<S>>::Undo),
45    CompoundScalar(<CompoundScalarMove<S> as Move<S>>::Undo),
46    ConflictRepair(<ConflictRepairMove<S> as Move<S>>::Undo),
47    RuntimeCompound(<RuntimeCompoundMove<S> as Move<S>>::Undo),
48}
49
50impl<S, V> Clone for ScalarMoveUnion<S, V>
51where
52    S: PlanningSolution,
53    V: Clone + PartialEq + Send + Sync + Debug + 'static,
54{
55    fn clone(&self) -> Self {
56        match self {
57            Self::Change(m) => Self::Change(m.clone()),
58            Self::Swap(m) => Self::Swap(*m),
59            Self::PillarChange(m) => Self::PillarChange(m.clone()),
60            Self::PillarSwap(m) => Self::PillarSwap(m.clone()),
61            Self::RuinRecreate(m) => Self::RuinRecreate(m.clone()),
62            Self::CompoundScalar(m) => Self::CompoundScalar(m.clone()),
63            Self::ConflictRepair(m) => Self::ConflictRepair(m.clone()),
64            Self::RuntimeCompound(m) => Self::RuntimeCompound(m.clone()),
65        }
66    }
67}
68
69impl<S, V> Debug for ScalarMoveUnion<S, V>
70where
71    S: PlanningSolution,
72    V: Clone + PartialEq + Send + Sync + Debug + 'static,
73{
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        match self {
76            Self::Change(m) => m.fmt(f),
77            Self::Swap(m) => m.fmt(f),
78            Self::PillarChange(m) => m.fmt(f),
79            Self::PillarSwap(m) => m.fmt(f),
80            Self::RuinRecreate(m) => m.fmt(f),
81            Self::CompoundScalar(m) => m.fmt(f),
82            Self::ConflictRepair(m) => m.fmt(f),
83            Self::RuntimeCompound(m) => m.fmt(f),
84        }
85    }
86}
87
88impl<S, V> Move<S> for ScalarMoveUnion<S, V>
89where
90    S: PlanningSolution,
91    V: Clone + PartialEq + Send + Sync + Debug + 'static,
92{
93    type Undo = ScalarMoveUnionUndo<S, V>;
94
95    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
96        match self {
97            Self::Change(m) => m.is_doable(score_director),
98            Self::Swap(m) => m.is_doable(score_director),
99            Self::PillarChange(m) => m.is_doable(score_director),
100            Self::PillarSwap(m) => m.is_doable(score_director),
101            Self::RuinRecreate(m) => m.is_doable(score_director),
102            Self::CompoundScalar(m) => m.is_doable(score_director),
103            Self::ConflictRepair(m) => m.is_doable(score_director),
104            Self::RuntimeCompound(m) => m.is_doable(score_director),
105        }
106    }
107
108    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
109        match self {
110            Self::Change(m) => ScalarMoveUnionUndo::Change(m.do_move(score_director)),
111            Self::Swap(m) => ScalarMoveUnionUndo::Swap(m.do_move(score_director)),
112            Self::PillarChange(m) => ScalarMoveUnionUndo::PillarChange(m.do_move(score_director)),
113            Self::PillarSwap(m) => ScalarMoveUnionUndo::PillarSwap(m.do_move(score_director)),
114            Self::RuinRecreate(m) => ScalarMoveUnionUndo::RuinRecreate(m.do_move(score_director)),
115            Self::CompoundScalar(m) => {
116                ScalarMoveUnionUndo::CompoundScalar(m.do_move(score_director))
117            }
118            Self::ConflictRepair(m) => {
119                ScalarMoveUnionUndo::ConflictRepair(m.do_move(score_director))
120            }
121            Self::RuntimeCompound(m) => {
122                ScalarMoveUnionUndo::RuntimeCompound(m.do_move(score_director))
123            }
124        }
125    }
126
127    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
128        match (self, undo) {
129            (Self::Change(m), ScalarMoveUnionUndo::Change(undo)) => {
130                m.undo_move(score_director, undo)
131            }
132            (Self::Swap(m), ScalarMoveUnionUndo::Swap(undo)) => m.undo_move(score_director, undo),
133            (Self::PillarChange(m), ScalarMoveUnionUndo::PillarChange(undo)) => {
134                m.undo_move(score_director, undo)
135            }
136            (Self::PillarSwap(m), ScalarMoveUnionUndo::PillarSwap(undo)) => {
137                m.undo_move(score_director, undo)
138            }
139            (Self::RuinRecreate(m), ScalarMoveUnionUndo::RuinRecreate(undo)) => {
140                m.undo_move(score_director, undo)
141            }
142            (Self::CompoundScalar(m), ScalarMoveUnionUndo::CompoundScalar(undo)) => {
143                m.undo_move(score_director, undo)
144            }
145            (Self::ConflictRepair(m), ScalarMoveUnionUndo::ConflictRepair(undo)) => {
146                m.undo_move(score_director, undo)
147            }
148            (Self::RuntimeCompound(m), ScalarMoveUnionUndo::RuntimeCompound(undo)) => {
149                m.undo_move(score_director, undo)
150            }
151            _ => panic!("scalar move undo shape must match move shape"),
152        }
153    }
154
155    fn descriptor_index(&self) -> usize {
156        match self {
157            Self::Change(m) => m.descriptor_index(),
158            Self::Swap(m) => m.descriptor_index(),
159            Self::PillarChange(m) => m.descriptor_index(),
160            Self::PillarSwap(m) => m.descriptor_index(),
161            Self::RuinRecreate(m) => m.descriptor_index(),
162            Self::CompoundScalar(m) => m.descriptor_index(),
163            Self::ConflictRepair(m) => m.descriptor_index(),
164            Self::RuntimeCompound(m) => m.descriptor_index(),
165        }
166    }
167
168    fn entity_indices(&self) -> &[usize] {
169        match self {
170            Self::Change(m) => m.entity_indices(),
171            Self::Swap(m) => m.entity_indices(),
172            Self::PillarChange(m) => m.entity_indices(),
173            Self::PillarSwap(m) => m.entity_indices(),
174            Self::RuinRecreate(m) => m.entity_indices(),
175            Self::CompoundScalar(m) => m.entity_indices(),
176            Self::ConflictRepair(m) => m.entity_indices(),
177            Self::RuntimeCompound(m) => m.entity_indices(),
178        }
179    }
180
181    fn variable_name(&self) -> &str {
182        match self {
183            Self::Change(m) => m.variable_name(),
184            Self::Swap(m) => m.variable_name(),
185            Self::PillarChange(m) => m.variable_name(),
186            Self::PillarSwap(m) => m.variable_name(),
187            Self::RuinRecreate(m) => m.variable_name(),
188            Self::CompoundScalar(m) => m.variable_name(),
189            Self::ConflictRepair(m) => m.variable_name(),
190            Self::RuntimeCompound(m) => m.variable_name(),
191        }
192    }
193
194    fn telemetry_label(&self) -> &'static str {
195        match self {
196            Self::Change(m) => m.telemetry_label(),
197            Self::Swap(m) => m.telemetry_label(),
198            Self::PillarChange(m) => m.telemetry_label(),
199            Self::PillarSwap(m) => m.telemetry_label(),
200            Self::RuinRecreate(m) => m.telemetry_label(),
201            Self::CompoundScalar(m) => m.telemetry_label(),
202            Self::ConflictRepair(m) => m.telemetry_label(),
203            Self::RuntimeCompound(m) => m.telemetry_label(),
204        }
205    }
206
207    fn requires_hard_improvement(&self) -> bool {
208        match self {
209            Self::Change(m) => m.requires_hard_improvement(),
210            Self::Swap(m) => m.requires_hard_improvement(),
211            Self::PillarChange(m) => m.requires_hard_improvement(),
212            Self::PillarSwap(m) => m.requires_hard_improvement(),
213            Self::RuinRecreate(m) => m.requires_hard_improvement(),
214            Self::CompoundScalar(m) => m.requires_hard_improvement(),
215            Self::ConflictRepair(m) => m.requires_hard_improvement(),
216            Self::RuntimeCompound(m) => m.requires_hard_improvement(),
217        }
218    }
219
220    fn requires_score_improvement(&self) -> bool {
221        match self {
222            Self::Change(m) => m.requires_score_improvement(),
223            Self::Swap(m) => m.requires_score_improvement(),
224            Self::PillarChange(m) => m.requires_score_improvement(),
225            Self::PillarSwap(m) => m.requires_score_improvement(),
226            Self::RuinRecreate(m) => m.requires_score_improvement(),
227            Self::CompoundScalar(m) => m.requires_score_improvement(),
228            Self::ConflictRepair(m) => m.requires_score_improvement(),
229            Self::RuntimeCompound(m) => m.requires_score_improvement(),
230        }
231    }
232
233    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
234        match self {
235            Self::Change(m) => m.tabu_signature(score_director),
236            Self::Swap(m) => m.tabu_signature(score_director),
237            Self::PillarChange(m) => m.tabu_signature(score_director),
238            Self::PillarSwap(m) => m.tabu_signature(score_director),
239            Self::RuinRecreate(m) => m.tabu_signature(score_director),
240            Self::CompoundScalar(m) => m.tabu_signature(score_director),
241            Self::ConflictRepair(m) => m.tabu_signature(score_director),
242            Self::RuntimeCompound(m) => m.tabu_signature(score_director),
243        }
244    }
245
246    fn candidate_trace_identity(&self) -> Option<CandidateTraceIdentity> {
247        match self {
248            Self::Change(m) => m.candidate_trace_identity(),
249            Self::Swap(m) => m.candidate_trace_identity(),
250            Self::PillarChange(m) => m.candidate_trace_identity(),
251            Self::PillarSwap(m) => m.candidate_trace_identity(),
252            Self::RuinRecreate(m) => m.candidate_trace_identity(),
253            Self::CompoundScalar(m) => m.candidate_trace_identity(),
254            Self::ConflictRepair(m) => m.candidate_trace_identity(),
255            Self::RuntimeCompound(m) => m.candidate_trace_identity(),
256        }
257    }
258
259    fn for_each_affected_entity(&self, visitor: &mut dyn FnMut(super::MoveAffectedEntity<'_>)) {
260        match self {
261            Self::Change(m) => m.for_each_affected_entity(visitor),
262            Self::Swap(m) => m.for_each_affected_entity(visitor),
263            Self::PillarChange(m) => m.for_each_affected_entity(visitor),
264            Self::PillarSwap(m) => m.for_each_affected_entity(visitor),
265            Self::RuinRecreate(m) => m.for_each_affected_entity(visitor),
266            Self::CompoundScalar(m) => m.for_each_affected_entity(visitor),
267            Self::ConflictRepair(m) => m.for_each_affected_entity(visitor),
268            Self::RuntimeCompound(m) => m.for_each_affected_entity(visitor),
269        }
270    }
271}