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::Move;
18
19/// A move that swaps values between two entities.
20///
21/// Stores entity indices and typed function pointers for zero-erasure access.
22/// Undo is handled by `RecordingDirector`, not by this move.
23///
24/// # Type Parameters
25/// * `S` - The planning solution type
26/// * `V` - The variable value type
27///
28/// # Example
29/// ```
30/// use solverforge_solver::heuristic::r#move::SwapMove;
31/// use solverforge_core::domain::PlanningSolution;
32/// use solverforge_core::score::SoftScore;
33///
34/// #[derive(Clone)]
35/// struct Sol { values: Vec<Option<i32>>, score: Option<SoftScore> }
36///
37/// impl PlanningSolution for Sol {
38///     type Score = SoftScore;
39///     fn score(&self) -> Option<Self::Score> { self.score }
40///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
41/// }
42///
43/// // Typed getter/setter with zero erasure
44/// fn get_v(s: &Sol, idx: usize) -> Option<i32> { s.values.get(idx).copied().flatten() }
45/// fn set_v(s: &mut Sol, idx: usize, v: Option<i32>) { if let Some(x) = s.values.get_mut(idx) { *x = v; } }
46///
47/// // Swap values between entities 0 and 1
48/// let swap = SwapMove::<Sol, i32>::new(0, 1, get_v, set_v, "value", 0);
49/// ```
50pub struct SwapMove<S, V> {
51    left_entity_index: usize,
52    right_entity_index: usize,
53    // Typed getter function pointer - zero erasure.
54    getter: fn(&S, usize) -> Option<V>,
55    // Typed setter function pointer - zero erasure.
56    setter: fn(&mut S, usize, Option<V>),
57    variable_name: &'static str,
58    descriptor_index: usize,
59    // Store indices inline for entity_indices() to return a slice.
60    indices: [usize; 2],
61}
62
63impl<S, V> Clone for SwapMove<S, V> {
64    fn clone(&self) -> Self {
65        *self
66    }
67}
68
69impl<S, V> Copy for SwapMove<S, V> {}
70
71impl<S, V: Debug> Debug for SwapMove<S, V> {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        f.debug_struct("SwapMove")
74            .field("left_entity_index", &self.left_entity_index)
75            .field("right_entity_index", &self.right_entity_index)
76            .field("descriptor_index", &self.descriptor_index)
77            .field("variable_name", &self.variable_name)
78            .finish()
79    }
80}
81
82impl<S, V> SwapMove<S, V> {
83    /// Creates a new swap move with typed function pointers.
84    ///
85    /// # Arguments
86    /// * `left_entity_index` - Index of the first entity
87    /// * `right_entity_index` - Index of the second entity
88    /// * `getter` - Typed getter function pointer
89    /// * `setter` - Typed setter function pointer
90    /// * `variable_name` - Name of the variable being swapped
91    /// * `descriptor_index` - Index in the entity descriptor
92    pub fn new(
93        left_entity_index: usize,
94        right_entity_index: usize,
95        getter: fn(&S, usize) -> Option<V>,
96        setter: fn(&mut S, usize, Option<V>),
97        variable_name: &'static str,
98        descriptor_index: usize,
99    ) -> Self {
100        Self {
101            left_entity_index,
102            right_entity_index,
103            getter,
104            setter,
105            variable_name,
106            descriptor_index,
107            indices: [left_entity_index, right_entity_index],
108        }
109    }
110
111    pub fn left_entity_index(&self) -> usize {
112        self.left_entity_index
113    }
114
115    pub fn right_entity_index(&self) -> usize {
116        self.right_entity_index
117    }
118}
119
120impl<S, V> Move<S> for SwapMove<S, V>
121where
122    S: PlanningSolution,
123    V: Clone + PartialEq + Send + Sync + Debug + 'static,
124{
125    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
126        // Can't swap with self
127        if self.left_entity_index == self.right_entity_index {
128            return false;
129        }
130
131        // Get current values using typed getter - zero erasure
132        let left_val = (self.getter)(score_director.working_solution(), self.left_entity_index);
133        let right_val = (self.getter)(score_director.working_solution(), self.right_entity_index);
134
135        // Swap only makes sense if values differ
136        left_val != right_val
137    }
138
139    fn do_move<D: Director<S>>(&self, score_director: &mut D) {
140        // Get both values using typed getter - zero erasure
141        let left_value = (self.getter)(score_director.working_solution(), self.left_entity_index);
142        let right_value = (self.getter)(score_director.working_solution(), self.right_entity_index);
143
144        // Notify before changes
145        score_director.before_variable_changed(self.descriptor_index, self.left_entity_index);
146        score_director.before_variable_changed(self.descriptor_index, self.right_entity_index);
147
148        // Swap: left gets right's value, right gets left's value
149        (self.setter)(
150            score_director.working_solution_mut(),
151            self.left_entity_index,
152            right_value.clone(),
153        );
154        (self.setter)(
155            score_director.working_solution_mut(),
156            self.right_entity_index,
157            left_value.clone(),
158        );
159
160        // Notify after changes
161        score_director.after_variable_changed(self.descriptor_index, self.left_entity_index);
162        score_director.after_variable_changed(self.descriptor_index, self.right_entity_index);
163
164        // Register typed undo closure - swap back
165        let setter = self.setter;
166        let left_idx = self.left_entity_index;
167        let right_idx = self.right_entity_index;
168        score_director.register_undo(Box::new(move |s: &mut S| {
169            // Restore original values
170            setter(s, left_idx, left_value);
171            setter(s, right_idx, right_value);
172        }));
173    }
174
175    fn descriptor_index(&self) -> usize {
176        self.descriptor_index
177    }
178
179    fn entity_indices(&self) -> &[usize] {
180        &self.indices
181    }
182
183    fn variable_name(&self) -> &str {
184        self.variable_name
185    }
186}