Skip to main content

solverforge_solver/heuristic/move/
list_reverse.rs

1/* ListReverseMove - reverses a segment within a list variable.
2
3This move reverses the order of elements in a range. Essential for
4TSP 2-opt optimization where reversing a tour segment can reduce distance.
5
6# Zero-Erasure Design
7
8Uses concrete function pointers for list operations. No `dyn Any`, no downcasting.
9*/
10
11use std::fmt::Debug;
12use std::marker::PhantomData;
13
14use smallvec::SmallVec;
15use solverforge_core::domain::PlanningSolution;
16use solverforge_scoring::Director;
17
18use super::metadata::{
19    encode_option_debug, encode_usize, scoped_move_identity, MoveTabuScope, ScopedValueTabuToken,
20    TABU_OP_LIST_REVERSE,
21};
22use super::{Move, MoveTabuSignature};
23
24/// A move that reverses a segment within a list.
25///
26/// This is the fundamental 2-opt move for TSP. Reversing a segment of the tour
27/// can significantly reduce total distance by eliminating crossing edges.
28///
29/// # Type Parameters
30/// * `S` - The planning solution type
31/// * `V` - The list element value type
32///
33/// # Example
34///
35/// ```
36/// use solverforge_solver::heuristic::r#move::ListReverseMove;
37/// use solverforge_core::domain::PlanningSolution;
38/// use solverforge_core::score::SoftScore;
39///
40/// #[derive(Clone, Debug)]
41/// struct Tour { cities: Vec<i32>, score: Option<SoftScore> }
42///
43/// impl PlanningSolution for Tour {
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/// fn list_len(s: &Tour, _: usize) -> usize { s.cities.len() }
50/// fn list_get(s: &Tour, _: usize, pos: usize) -> Option<i32> { s.cities.get(pos).copied() }
51/// fn list_reverse(s: &mut Tour, _: usize, start: usize, end: usize) {
52///     s.cities[start..end].reverse();
53/// }
54///
55/// // Reverse segment [1..4) in tour: [A, B, C, D, E] -> [A, D, C, B, E]
56/// let m = ListReverseMove::<Tour, i32>::new(
57///     0, 1, 4,
58///     list_len, list_get, list_reverse,
59///     "cities", 0,
60/// );
61/// ```
62pub struct ListReverseMove<S, V> {
63    // Entity index
64    entity_index: usize,
65    // Start of range to reverse (inclusive)
66    start: usize,
67    // End of range to reverse (exclusive)
68    end: usize,
69    list_len: fn(&S, usize) -> usize,
70    list_get: fn(&S, usize, usize) -> Option<V>,
71    // Reverse elements in range [start, end)
72    list_reverse: fn(&mut S, usize, usize, usize),
73    variable_name: &'static str,
74    descriptor_index: usize,
75    _phantom: PhantomData<fn() -> V>,
76}
77
78impl<S, V> Clone for ListReverseMove<S, V> {
79    fn clone(&self) -> Self {
80        *self
81    }
82}
83
84impl<S, V> Copy for ListReverseMove<S, V> {}
85
86impl<S, V: Debug> Debug for ListReverseMove<S, V> {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        f.debug_struct("ListReverseMove")
89            .field("entity", &self.entity_index)
90            .field("range", &(self.start..self.end))
91            .field("variable_name", &self.variable_name)
92            .finish()
93    }
94}
95
96impl<S, V> ListReverseMove<S, V> {
97    /* Creates a new list reverse move with concrete function pointers.
98
99    # Arguments
100    * `entity_index` - Entity index
101    * `start` - Start of range (inclusive)
102    * `end` - End of range (exclusive)
103    * `list_len` - Function to get list length
104    * `list_reverse` - Function to reverse elements in range
105    * `variable_name` - Name of the list variable
106    * `descriptor_index` - Entity descriptor index
107    */
108    #[allow(clippy::too_many_arguments)]
109    pub fn new(
110        entity_index: usize,
111        start: usize,
112        end: usize,
113        list_len: fn(&S, usize) -> usize,
114        list_get: fn(&S, usize, usize) -> Option<V>,
115        list_reverse: fn(&mut S, usize, usize, usize),
116        variable_name: &'static str,
117        descriptor_index: usize,
118    ) -> Self {
119        Self {
120            entity_index,
121            start,
122            end,
123            list_len,
124            list_get,
125            list_reverse,
126            variable_name,
127            descriptor_index,
128            _phantom: PhantomData,
129        }
130    }
131
132    pub fn entity_index(&self) -> usize {
133        self.entity_index
134    }
135
136    pub fn start(&self) -> usize {
137        self.start
138    }
139
140    pub fn end(&self) -> usize {
141        self.end
142    }
143
144    pub fn segment_len(&self) -> usize {
145        self.end.saturating_sub(self.start)
146    }
147}
148
149impl<S, V> Move<S> for ListReverseMove<S, V>
150where
151    S: PlanningSolution,
152    V: Clone + Send + Sync + Debug + 'static,
153{
154    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
155        let solution = score_director.working_solution();
156
157        // Range must have at least 2 elements to be meaningful
158        if self.end <= self.start + 1 {
159            return false;
160        }
161
162        // Check range is within bounds
163        let len = (self.list_len)(solution, self.entity_index);
164        if self.end > len {
165            return false;
166        }
167
168        true
169    }
170
171    fn do_move<D: Director<S>>(&self, score_director: &mut D) {
172        // Notify before change
173        score_director.before_variable_changed(self.descriptor_index, self.entity_index);
174
175        // Reverse the segment
176        (self.list_reverse)(
177            score_director.working_solution_mut(),
178            self.entity_index,
179            self.start,
180            self.end,
181        );
182
183        // Notify after change
184        score_director.after_variable_changed(self.descriptor_index, self.entity_index);
185
186        // Register undo - reversing twice restores original
187        let list_reverse = self.list_reverse;
188        let entity = self.entity_index;
189        let start = self.start;
190        let end = self.end;
191
192        score_director.register_undo(Box::new(move |s: &mut S| {
193            list_reverse(s, entity, start, end);
194        }));
195    }
196
197    fn descriptor_index(&self) -> usize {
198        self.descriptor_index
199    }
200
201    fn entity_indices(&self) -> &[usize] {
202        std::slice::from_ref(&self.entity_index)
203    }
204
205    fn variable_name(&self) -> &str {
206        self.variable_name
207    }
208
209    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
210        let mut value_ids: SmallVec<[u64; 2]> = SmallVec::new();
211        for pos in self.start..self.end {
212            let value = (self.list_get)(score_director.working_solution(), self.entity_index, pos);
213            value_ids.push(encode_option_debug(value.as_ref()));
214        }
215        let entity_id = encode_usize(self.entity_index);
216        let scope = MoveTabuScope::new(self.descriptor_index, self.variable_name);
217        let destination_value_tokens: SmallVec<[ScopedValueTabuToken; 2]> = value_ids
218            .iter()
219            .copied()
220            .map(|value_id| scope.value_token(value_id))
221            .collect();
222        let move_id = scoped_move_identity(
223            scope,
224            TABU_OP_LIST_REVERSE,
225            [entity_id, encode_usize(self.start), encode_usize(self.end)],
226        );
227
228        MoveTabuSignature::new(scope, move_id.clone(), move_id)
229            .with_entity_tokens([scope.entity_token(entity_id)])
230            .with_destination_value_tokens(destination_value_tokens)
231    }
232}