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