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 concrete 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 concrete function pointers for zero-erasure access.
26/// `do_move` returns the two previous values as typed undo data.
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/// // Concrete 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    // Concrete getter function pointer - zero erasure.
58    getter: fn(&S, usize, usize) -> Option<V>,
59    // Concrete 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 concrete 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` - Concrete getter function pointer
95    /// * `setter` - Concrete 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    type Undo = (Option<V>, Option<V>);
134
135    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
136        // Can't swap with self
137        if self.left_entity_index == self.right_entity_index {
138            return false;
139        }
140
141        // Get current values using concrete getter - zero erasure
142        let left_val = (self.getter)(
143            score_director.working_solution(),
144            self.left_entity_index,
145            self.variable_index,
146        );
147        let right_val = (self.getter)(
148            score_director.working_solution(),
149            self.right_entity_index,
150            self.variable_index,
151        );
152
153        // Swap only makes sense if values differ
154        left_val != right_val
155    }
156
157    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
158        // Get both values using concrete getter - zero erasure
159        let left_value = (self.getter)(
160            score_director.working_solution(),
161            self.left_entity_index,
162            self.variable_index,
163        );
164        let right_value = (self.getter)(
165            score_director.working_solution(),
166            self.right_entity_index,
167            self.variable_index,
168        );
169
170        // Notify before changes
171        score_director.before_variable_changed(self.descriptor_index, self.left_entity_index);
172        score_director.before_variable_changed(self.descriptor_index, self.right_entity_index);
173
174        // Swap: left gets right's value, right gets left's value
175        (self.setter)(
176            score_director.working_solution_mut(),
177            self.left_entity_index,
178            self.variable_index,
179            right_value.clone(),
180        );
181        (self.setter)(
182            score_director.working_solution_mut(),
183            self.right_entity_index,
184            self.variable_index,
185            left_value.clone(),
186        );
187
188        // Notify after changes
189        score_director.after_variable_changed(self.descriptor_index, self.left_entity_index);
190        score_director.after_variable_changed(self.descriptor_index, self.right_entity_index);
191
192        (left_value, right_value)
193    }
194
195    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
196        score_director.before_variable_changed(self.descriptor_index, self.left_entity_index);
197        score_director.before_variable_changed(self.descriptor_index, self.right_entity_index);
198        (self.setter)(
199            score_director.working_solution_mut(),
200            self.left_entity_index,
201            self.variable_index,
202            undo.0,
203        );
204        (self.setter)(
205            score_director.working_solution_mut(),
206            self.right_entity_index,
207            self.variable_index,
208            undo.1,
209        );
210        score_director.after_variable_changed(self.descriptor_index, self.left_entity_index);
211        score_director.after_variable_changed(self.descriptor_index, self.right_entity_index);
212    }
213
214    fn descriptor_index(&self) -> usize {
215        self.descriptor_index
216    }
217
218    fn entity_indices(&self) -> &[usize] {
219        &self.indices
220    }
221
222    fn variable_name(&self) -> &str {
223        self.variable_name
224    }
225
226    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
227        let left_val = (self.getter)(
228            score_director.working_solution(),
229            self.left_entity_index,
230            self.variable_index,
231        );
232        let right_val = (self.getter)(
233            score_director.working_solution(),
234            self.right_entity_index,
235            self.variable_index,
236        );
237        let left_id = encode_option_debug(left_val.as_ref());
238        let right_id = encode_option_debug(right_val.as_ref());
239        let left_entity_id = encode_usize(self.left_entity_index);
240        let right_entity_id = encode_usize(self.right_entity_index);
241        let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
242        let entity_pair = ordered_coordinate_pair((left_entity_id, 0), (right_entity_id, 0));
243        let move_id = scoped_move_identity(
244            scope,
245            TABU_OP_SWAP,
246            entity_pair.into_iter().map(|(entity_id, _)| entity_id),
247        );
248
249        MoveTabuSignature::new(scope, move_id.clone(), move_id)
250            .with_entity_tokens([
251                scope.entity_token(left_entity_id),
252                scope.entity_token(right_entity_id),
253            ])
254            .with_destination_value_tokens([
255                scope.value_token(right_id),
256                scope.value_token(left_id),
257            ])
258    }
259}