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