Skip to main content

solverforge_solver/heuristic/move/
swap.rs

1/* SwapMove - exchanges values between two entities.
2
3This move swaps the values of a planning variable between two entities.
4Useful for permutation-based problems.
5
6# Zero-Erasure Design
7
8SwapMove uses typed function pointers instead of `dyn Any` for complete
9compile-time type safety. No runtime type checks or downcasting.
10*/
11
12use std::fmt::Debug;
13
14use solverforge_core::domain::PlanningSolution;
15use solverforge_scoring::Director;
16
17use super::metadata::{
18    encode_option_debug, encode_usize, ordered_coordinate_pair, scoped_move_identity,
19    MoveTabuScope, TABU_OP_SWAP,
20};
21use super::{Move, MoveTabuSignature};
22
23/// A move that swaps values between two entities.
24///
25/// Stores entity indices and typed function pointers for zero-erasure access.
26/// Undo is handled by `RecordingDirector`, not by this move.
27///
28/// # Type Parameters
29/// * `S` - The planning solution type
30/// * `V` - The variable value type
31///
32/// # Example
33/// ```
34/// use solverforge_solver::heuristic::r#move::SwapMove;
35/// use solverforge_core::domain::PlanningSolution;
36/// use solverforge_core::score::SoftScore;
37///
38/// #[derive(Clone)]
39/// struct Sol { values: Vec<Option<i32>>, score: Option<SoftScore> }
40///
41/// impl PlanningSolution for Sol {
42///     type Score = SoftScore;
43///     fn score(&self) -> Option<Self::Score> { self.score }
44///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
45/// }
46///
47/// // Typed getter/setter with zero erasure
48/// fn get_v(s: &Sol, idx: usize, _var: usize) -> Option<i32> { s.values.get(idx).copied().flatten() }
49/// fn set_v(s: &mut Sol, idx: usize, _var: usize, v: Option<i32>) { if let Some(x) = s.values.get_mut(idx) { *x = v; } }
50///
51/// // Swap values between entities 0 and 1
52/// let swap = SwapMove::<Sol, i32>::new(0, 1, get_v, set_v, 0, "value", 0);
53/// ```
54pub struct SwapMove<S, V> {
55    left_entity_index: usize,
56    right_entity_index: usize,
57    // Typed getter function pointer - zero erasure.
58    getter: fn(&S, usize, usize) -> Option<V>,
59    // Typed setter function pointer - zero erasure.
60    setter: fn(&mut S, usize, usize, Option<V>),
61    variable_index: usize,
62    variable_name: &'static str,
63    descriptor_index: usize,
64    // Store indices inline for entity_indices() to return a slice.
65    indices: [usize; 2],
66}
67
68impl<S, V> Clone for SwapMove<S, V> {
69    fn clone(&self) -> Self {
70        *self
71    }
72}
73
74impl<S, V> Copy for SwapMove<S, V> {}
75
76impl<S, V: Debug> Debug for SwapMove<S, V> {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        f.debug_struct("SwapMove")
79            .field("left_entity_index", &self.left_entity_index)
80            .field("right_entity_index", &self.right_entity_index)
81            .field("descriptor_index", &self.descriptor_index)
82            .field("variable_index", &self.variable_index)
83            .field("variable_name", &self.variable_name)
84            .finish()
85    }
86}
87
88impl<S, V> SwapMove<S, V> {
89    /// Creates a new swap move with typed function pointers.
90    ///
91    /// # Arguments
92    /// * `left_entity_index` - Index of the first entity
93    /// * `right_entity_index` - Index of the second entity
94    /// * `getter` - Typed getter function pointer
95    /// * `setter` - Typed setter function pointer
96    /// * `variable_name` - Name of the variable being swapped
97    /// * `descriptor_index` - Index in the entity descriptor
98    pub fn new(
99        left_entity_index: usize,
100        right_entity_index: usize,
101        getter: fn(&S, usize, usize) -> Option<V>,
102        setter: fn(&mut S, usize, usize, Option<V>),
103        variable_index: usize,
104        variable_name: &'static str,
105        descriptor_index: usize,
106    ) -> Self {
107        Self {
108            left_entity_index,
109            right_entity_index,
110            getter,
111            setter,
112            variable_index,
113            variable_name,
114            descriptor_index,
115            indices: [left_entity_index, right_entity_index],
116        }
117    }
118
119    pub fn left_entity_index(&self) -> usize {
120        self.left_entity_index
121    }
122
123    pub fn right_entity_index(&self) -> usize {
124        self.right_entity_index
125    }
126}
127
128impl<S, V> Move<S> for SwapMove<S, V>
129where
130    S: PlanningSolution,
131    V: Clone + PartialEq + Send + Sync + Debug + 'static,
132{
133    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
134        // Can't swap with self
135        if self.left_entity_index == self.right_entity_index {
136            return false;
137        }
138
139        // Get current values using typed getter - zero erasure
140        let left_val = (self.getter)(
141            score_director.working_solution(),
142            self.left_entity_index,
143            self.variable_index,
144        );
145        let right_val = (self.getter)(
146            score_director.working_solution(),
147            self.right_entity_index,
148            self.variable_index,
149        );
150
151        // Swap only makes sense if values differ
152        left_val != right_val
153    }
154
155    fn do_move<D: Director<S>>(&self, score_director: &mut D) {
156        // Get both values using typed getter - zero erasure
157        let left_value = (self.getter)(
158            score_director.working_solution(),
159            self.left_entity_index,
160            self.variable_index,
161        );
162        let right_value = (self.getter)(
163            score_director.working_solution(),
164            self.right_entity_index,
165            self.variable_index,
166        );
167
168        // Notify before changes
169        score_director.before_variable_changed(self.descriptor_index, self.left_entity_index);
170        score_director.before_variable_changed(self.descriptor_index, self.right_entity_index);
171
172        // Swap: left gets right's value, right gets left's value
173        (self.setter)(
174            score_director.working_solution_mut(),
175            self.left_entity_index,
176            self.variable_index,
177            right_value.clone(),
178        );
179        (self.setter)(
180            score_director.working_solution_mut(),
181            self.right_entity_index,
182            self.variable_index,
183            left_value.clone(),
184        );
185
186        // Notify after changes
187        score_director.after_variable_changed(self.descriptor_index, self.left_entity_index);
188        score_director.after_variable_changed(self.descriptor_index, self.right_entity_index);
189
190        // Register typed undo closure - swap back
191        let setter = self.setter;
192        let left_idx = self.left_entity_index;
193        let right_idx = self.right_entity_index;
194        let variable_index = self.variable_index;
195        score_director.register_undo(Box::new(move |s: &mut S| {
196            // Restore original values
197            setter(s, left_idx, variable_index, left_value);
198            setter(s, right_idx, variable_index, right_value);
199        }));
200    }
201
202    fn descriptor_index(&self) -> usize {
203        self.descriptor_index
204    }
205
206    fn entity_indices(&self) -> &[usize] {
207        &self.indices
208    }
209
210    fn variable_name(&self) -> &str {
211        self.variable_name
212    }
213
214    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
215        let left_val = (self.getter)(
216            score_director.working_solution(),
217            self.left_entity_index,
218            self.variable_index,
219        );
220        let right_val = (self.getter)(
221            score_director.working_solution(),
222            self.right_entity_index,
223            self.variable_index,
224        );
225        let left_id = encode_option_debug(left_val.as_ref());
226        let right_id = encode_option_debug(right_val.as_ref());
227        let left_entity_id = encode_usize(self.left_entity_index);
228        let right_entity_id = encode_usize(self.right_entity_index);
229        let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
230        let entity_pair = ordered_coordinate_pair((left_entity_id, 0), (right_entity_id, 0));
231        let move_id = scoped_move_identity(
232            scope,
233            TABU_OP_SWAP,
234            entity_pair.into_iter().map(|(entity_id, _)| entity_id),
235        );
236
237        MoveTabuSignature::new(scope, move_id.clone(), move_id)
238            .with_entity_tokens([
239                scope.entity_token(left_entity_id),
240                scope.entity_token(right_entity_id),
241            ])
242            .with_destination_value_tokens([
243                scope.value_token(right_id),
244                scope.value_token(left_id),
245            ])
246    }
247}